go 语言并发简单总结(一)

Go 语言并发简单总结(一)

——《go语言并发之道》

约束

  1. 特殊限制
    通过公约实现约束,例如团队规章或者代码库规范设置
  2. 词法约束
    将数据的读写处理暴露给需要它的并发进程,例如,使用使用闭包将某个channel的写入操作约束起来。

for-select循环

for{//无限循环或者使用range
	select{
	//使用channel进行作业
	}
}	

建议的使用场景:

  1. 向channel发送迭代变量
  2. 循环等待停止

防止goroutine泄露

为了能够及时地通知一个goroutine完成或取消工作,使用一个信号通道进行管理。在父子之间建立一个只读的channel,父goroutine将该channel传递给子goroutine,然后在想取消子goroutine时,关闭该channel

doWork := func(
	done <-chan interface{},
){
	go func(){
		for{//这里又用到了select循环模式
			select{
			case <-done:
				//done被关闭时,子goroutine结束工作
				return
			case:
				//正常处理工作
			}
		}
	}
}

错误处理

type Result struct{
	Error error,

	//other data or info,
}

通过构建一个包含错误信息的结构体,并在并发的子goroutine结束时返回该类型变量,在父进程中检查error来处理错误

pipeline

属性定义:

  • 一个输入的参数与返回值类型相同的stage
  • 一个stage必须通过编程语言实现之后才能被当作参数传递

对于Pipeline来说,处理方式分为流处理和批处理,对比如下:
流处理

multiply := func(value, multilier int) int{
	return multilier * value
}

使用流处理,对于内存占用空间消耗相对少,但是,不得不将pipeline放到循环结构中去,这限制了我们向重复利用的pipeline发送消息,而且对于程序的扩展性也不强。同时,尽管函数调用的代价很低,但是循环的每次迭代进行多次调用,并发性将受到威胁。

批处理

multiply := func(value []int, multilier int) int{
	multipliedValues := make([]int, len(values))
	for i,v := range values{
		multipliedValues = values[i] * multilier 
	}
	return multipliedValues 
} 

扇入扇出

考虑在多个goroutine上重用pipeline的单个stage以试图并行化来自上游的stage 的pull,这有助于提高pipeline的性能
在以下两个条件都成立的情况下,考虑使用扇出模式:

  • 不依赖于之前stage计算出的值
  • 运行需要很长时间

例如:

numFinders := runtime.NumCPU()
finders := make([]<-chan int, numFinders)
for i:= 0; i<numFinders; i++{
	finders[i] = primeFinder(done,randIntStream)
}

primeFinder是素数筛选函数

启动该stage的多个副本,这里副本数量由CPU核心数决定,实际使用过程中,可以采用一些经验性的测试确定CPU的最佳数量。

现在,我们开启了四个子goroutine,对应的输出了四个不同的channel,为了综合结果,使用扇入模式,将多个数据流复用或者合并成一个流

fanIn := func(
	done <-chan interface{},
	channels ...<-chan interface{},
) <-chan interface{}{
	var wg sync.WaitGroup
	multiplexedStream := make(chan interface{})

	multiplex := func(c<-chan interface){
		defer wg.Done()
		for i:=range c{
			select{
			case <-done:
				return
			case multiplexedStream <-i:
			}
		}
	}
	wg.Add(len(channels))
	for _,c:=range channels{
		go multiplex(c)
	}
	go func(){
		wg.Wait()
		close(multiplexedStream)
	}()
	return multiplexedStream
}

or-done-channel

操作来自系统各个部分的channel时,无法做出断言,因此得进行一些判断,在数量较多时,将会带来不必要的工作,此时,可以用一个goroutine解决这个问题,将我们关心的数据重点暴露出来,封装其他不必要部分。

orDone := func(done, c<- chan interface{}) <-chan interface{}{
	varStream := make(chan interface{})
	go func(){
		defer close(varStream)
		for{
			select{
			case <-done:
				return
			case var,ok := <-c:
			if ok == false{
				return
			}
			select{
			case valStream<-c:
			case <-done
			}
		}
	}()
	return valStream
}


for val := range orDone(done, myChan){
	//执行操作
}

tee-channel

传递一个读channel,返回两个相同值的单独的channel
实现略

队列排队

队列几乎不会加速程序运行的总时间,它只能让程序的行为有所不同
证明:
根据利特尔法则:L=λWL=\lambda * W

其中,L是系统中平均负载数, λ\lambda是负载的平均到达率,W是负载在系统中花费的平均时间。
如果我们在系统中增加队列,本质上是在增加L。那么要么是增大了λ\lambda——nL=nλWnL=n\lambda * W,要么是增大了W——nL=λnWnL=\lambda * nW,并不会减少负载在系统中的花费时间。
但是,利特尔法则不能预知的情况是处理请求的失败。某些情况下,队列的存在是必要的。

context包

理解Go Context机制
Go Context的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章