• gloang中的并发模式


    1. for-select循环:

      使用方式:for+select

    for { // loop
    	select {
    	// use channel
    	}
    }
    

      eg1: 非抢占式任务

    for { // loop
    	select {
    	case <- done:
            return
            default:
         // 进行非抢占任务 } }

      eg2: 迭代发送值

    for _, s := range []string{"t","e","s","t"}{ // loop
    	select {
    	case <-done:
    		return
    	case stringStream<-s:
    	}
    }

      

    2. 防止goroutine泄露的几种实践
      1.3版本后,golang引入了较好的GC机制,并且在不断的对其完善,解决了内存泄露的问题。但是没有解决goroutine泄露的情况。

      goroutie的最佳实践应该是,开的goroutine都应该让它正确的结束,否则就意味着goroutine leak,给goroutine分配的内存以及它所引用的内存都将无法被回收。

      一个典型的造成goroutine泄露的代码:

    func doWork(strings <-chan string) <-chan interface{}{
    	completed := make(chan interface{})
    	go func() {
    		defer close(completed)
    		for s := range strings{ // loop
    			completed <- s
    		}
    	}()
    	return completed
    }
    
    func main() {
            // nil channel	
        	doWork(nil)
    }
    

      上述代码中,doWork()函数中开了一个goroutine对 strings channel做一些操作,操作完成后返回。在main()中对其调用,但是传的参数strings是nil。golang中对nil channel的读取是阻塞的。因此,doWork()函数实际上被阻塞。主goroutine执行结束后,doWork()仍然在阻塞,就造成了goroutine泄露。

      因此,在使用goroutine时,还是要考虑正确的关闭当前的goroutine。参考了官方文档,goroutine有以下几种方式被终止:

      (1) 当它完成了当前的工作

      (2) 因为不可恢复的错误,不能继续工作

      (3) 当它被告知需要终止工作

      对于第一种方法,goroutine完成了当前的工作后自然会进行返回,对于第二种方法,goroutine会对抛出异常,因此我们用第三种方法来进行实践。

      方法1:在发送端关闭channel,channel关闭后,接收端不在接收

      

    func main() {
    	strings := make(chan string,10)
            // sender
    	go func() {
    		for{
    			select {
    			case <-time.After(10*time.Second):
    				close(strings)
    			case strings<-"sender":
    			}
    		}
    	}()
            // receiver
    	go func() {
        		for value := range dataCh {
    			log.Println(value)
    		}
    	}()
            time.Sleep(20*time.Second)
    }
    

      

      方法2:借助channel的特性,读取一个空的channel会阻塞,读取已经关闭的channel会返回类型的零值(引入额外的开销)

      我们对上述的代码修改,

    // param: done channe interface{}, strings <- chan string
    func doWork(done <-chan interface{},strings <-chan string) <-chan interface{}{
    	completed := make(chan interface{})
    	go func() {
    		defer close(completed)
    		for { // loop
    			select {
    			case s:=<-strings:
    				completed <- s
    			case <-done: // done 为nil时,阻塞  外部关闭done后,可以读取到值,return
    				return
    			}
    		}
    	}()
    	return completed
    }
    
    func main() {
    
    	done := make(chan interface{})
    	strings := make(chan string)
    	strings <- "11111"
    	doWork(done,strings)
    
    	go func() {
    		// 关闭done channel
    		close(done)
    	}()
    }
    

      

      方法3:sync.Context包(管理上下文)

      Context包中封装了多个函数,几个常用的有:WithCancel,WithTimeout,WithValue。我们可以使用其中的WithCancel()来防止goroutine泄露。

      对上述代码进行改写:

    // param: ctx context.Context, strings <- chan string
    func doWork(ctx context.Context ,strings <-chan string) <-chan interface{}{
    	completed := make(chan interface{})
    	go func() {
    		defer close(completed)
    		for { // loop
    			select {
    			case s:=<-strings:
    				completed <- s
    			case ctx.Done():
    				return
    			}
    		}
    	}()
    	return completed
    }
    
    func main() {
    	ctx, cancel := context.WithCancel(context.Background())
    	strings := make(chan string)
    	strings <- "11111"
    	doWork(ctx,strings)
    
    	time.Sleep(10*time.Second)
    	// close context
    	cancel()
    }
    

      本质上仍是在发送端对其进行了关闭。

  • 相关阅读:
    洛谷[P1002]过河卒
    ACM-Teleportation
    ACM-Team Tic Tac Toe
    Data_Structure04-树
    Data_Structure03-栈和队列
    Data_Structure02-线性表
    Data_Structure01-绪论
    C语言第二次实验报告
    C语言第一次实验报告
    mysql
  • 原文地址:https://www.cnblogs.com/ThomasYuan/p/10783798.html
Copyright © 2020-2023  润新知