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()
}

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

posted @ 2019-04-28 14:29  ThomasYuan  阅读(304)  评论(1编辑  收藏  举报