《Go语言并发之道》学习笔记之第4章 Go语言的并发模式
空接口interface{}
约束
保证并发操作安全的方法:
- 内存共享同步(sync.Mutex等)
- 通过通信同步(channel等)
其他隐式并发安全情况:
- 不可变数据
- 受保护数据
传递数据值的副本,非指向内存值的指针,不改变原始数据。
限制用来确保数据只能同时被一个进程访问。
存在两种可能约束:
- 特定约束
通过公约实现约束(大家约定好的做法)
约定loopData函数访问data,handleData channel输出data
data := make([]int, 4)
loopData := func(handleData chan<- int) {
defer close(handleData)
for i := range data {
handleData <- data[i]
}
}
handleData := make(chan int)
go loopData(handleData)
for num := range handleData {
fmt.Println(num)
}
- 词法约束
词法作用域用于并发进程的正确数据和并发原语,不可能出错。
chanOwner := func() <-chan int {
results := make(chan int, 5)
go func() {
defer close(results)
for i := 0; i < 5; i++ {
results <- i
}
}()
return results
}
consumer := func(results <-chan int) {
for result := range results {
fmt.Println("Received: %d\n", result)
}
fmt.Println("Done receiving!")
}
results := chanOwner()
consumer(results)
使用并发安全的数据结构的例子。
printData := func(wg *sync.WaitGroup, data []byte) {
defer wg.Done()
bar buff bytes.Buffer
for _, b := range data {
fmt.Fprintf(&buff, "%c", b)
}
fmt.Println(buff.String())
}
var wg sync.WaitGroup
wg.Add(2)
data := []byte("golang")
go printData(&wg, data[:3])
go printData(&wg, data[3:])
wg.Wait()
约束提高了性能并降低了开发人员的认知负担。
for-select循环
for { //无限循环或for-range语句循环
select {
//使用channel作业
}
}
向channel发送迭代变量
for _, s := range []string{"a", "b", "c"} {
select {
case <-done:
return
case stringStream <- s:
}
}
循环等待停止
for {
select {
case <-done:
return
default:
}
//非抢占式任务
}
for {
select {
case <-done:
return
default:
//非抢占式任务
}
}
防止goroutine泄漏
goroutine需要消耗资源,不会被运行时垃圾回收。
goroutine终止方式:
- 完成工作
- 不可恢复错误
- 被告知需要终止
确保子goroutine被清理
doWork := func(strings <-chan string) <-chan interface{} {
completed := make(chan interface{})
go func() {
defer fmt.Println("doWork exited.")
defer close(completed)
for s := range strings { //for s := range nil {,死锁
fmt.Println(s)
}
}()
return completed
}
doWork(nil)
//var strings chan string
//doWork(strings)
fmt.Println("Done.")
父子goroutine间建立信号通道(done,只读channel),父goroutine将channel传递给子goroutine,取消子goroutine时关闭channel。
package main
import (
"fmt"
"time"
)
func main() {
doWork := func(done <-chan interface{}, strings <-chan string) <-chan interface{} {
completed := make(chan interface{})
go func() {
defer fmt.Println("doWork exited.")
defer close(completed)
for {
select {
case s := <-strings: //nil
fmt.Println(s)
fmt.Println("nil不会执行")
case <-done:
return //退出子goroutine
}
}
}()
return completed
}
done := make(chan interface{})
terminated := doWork(done, nil)
go func() {
time.Sleep(10 * time.Second)
fmt.Println("Canceling doWork goroutine...")
close(done) //关闭子goroutine
}()
<-terminated //等待子goroutine
fmt.Println("Done.")
}
处理一个goroutine阻塞了向channel进行写入的情况。
newRandStream := func() <-chan int {
randStream := make(int)
go func() {
defer fmt.Println("newRandStream closure exited.") //永远不会运行
defer close(randStream)
for { //无退出
randStream <- rand.Int()
}
}()
return randStream
}
randStream := newRandStream()
fmt.Println("3 random ints:")
for i := 1; i <= 3; i++ {
fmt.Printf("%d: %d\n", i, <-randStream)
}
为生产者goroutine提供一个通知它退出的channel。
newRandStream := func(done <-chan interface{}) <-chan int {
randStream := make(int)
go func() {
defer fmt.Println("newRandStream closure exited.")
defer close(randStream)
for {
select {
case randStream <- rand.Int():
case <-done:
return //可以退出
}
}
}()
return randStream
}
done := make(chan interface{})
randStream := newRandStream(done)
fmt.Println("3 random ints:")
for i := 1; i <= 3; i++ {
fmt.Printf("%d: %d\n", i, <-randStream)
}
close(done) //关闭子goroutine
time.Sleep(1*time.Second)
如果goroutine负责创建子goroutine,它也负责可以确保它可以停止子goroutine。
or-channel
合并多个done channel成单一的done channel,这个复合done channel中任一done channel关闭或写入时,关闭整个done channel。(合并所有子goroutine的done channel,任一子goroutine执行结束,主goroutine继续执行,其他goroutine并未终止。)
or-channel通过递归和goroutine创建一个复合done channel。
package main
import (
"fmt"
"time"
)
func main() {
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} {
switch len(channels) {
case 0:
return nil
case 1:
return channels[0]
}
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2:
select {
case <-channels[0]:
case <-channels[1]:
}
default:
select {
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
case <-or(append(channels[3:], orDone)...):
}
}
}()
return orDone
}
sig := func(after time.Duration) <-chan interface{} {
c := make(chan interface{})
go func() {
defer close(c)
defer fmt.Println(after)
time.Sleep(after)
}()
return c
}
start := time.Now()
<-or(
sig(2*time.Hour),
sig(5*time.Minute),
sig(1*time.Second),
sig(2*time.Second),
sig(5*time.Second),
sig(1*time.Hour),
sig(1*time.Minute),
)
fmt.Printf("done after %v\n", time.Since(start))
time.Sleep(11*time.Second)
}
修改版本1
package main
import (
"fmt"
"time"
)
func sig(after time.Duration) <-chan interface{} {
c := make(chan interface{})
go func() {
defer close(c)
defer fmt.Println(after)
time.Sleep(after)
}()
return c
}
func main() {
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} {
switch len(channels) {
case 0:
data := make(chan interface{})
close(data)
return data
case 1:
return channels[0]
default:
orDone := make(chan interface{})
go func() {
defer close(orDone)
select {
case <-channels[0]:
case <-channels[1]:
case <-or(append(channels[2:], orDone)...):
}
}()
return orDone
}
}
start := time.Now()
<-or()
fmt.Printf("done after %v\n", time.Since(start))
start = time.Now()
<-or(sig(1 * time.Second))
fmt.Printf("done after %v\n", time.Since(start))
start = time.Now()
<-or(sig(1*time.Second), sig(2*time.Second))
time.Sleep(3*time.Second)
fmt.Printf("done after %v\n", time.Since(start))
start = time.Now()
<-or(
sig(2*time.Second),
sig(5*time.Second),
sig(1*time.Second),
sig(1*time.Hour),
sig(1*time.Minute),
)
time.Sleep(10*time.Second)
fmt.Printf("done after %v\n", time.Since(start))
}
修改版本2
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan interface{})
sig := func(after time.Duration) {
defer fmt.Println(after)
time.Sleep(after)
done <- struct{}{}
}
start := time.Now()
go sig(2 * time.Second)
go sig(5 * time.Second)
go sig(1 * time.Second)
go sig(1 * time.Hour)
go sig(1 * time.Minute)
<-done
fmt.Printf("done after %v\n", time.Since(start))
}
错误处理
错误处理最根本的问题是谁应该负责处理错误。
不正确的示例。
只打印错误信息,未处理。
package main
import (
"fmt"
"net/http"
)
func main() {
checkStatus := func(
done <-chan interface{},
urls ...string,
) <-chan *http.Response {
responses := make(chan *http.Response)
go func() {
defer close(responses)
for _, url := range urls {
resp, err := http.Get(url)
if err != nil {
fmt.Println(err) // <1>
continue
}
select {
case <-done:
return
case responses <- resp:
}
}
}()
return responses
}
done := make(chan interface{})
defer close(done)
urls := []string{"https://www.google.com", "https://badhost"}
for response := range checkStatus(done, urls...) {
fmt.Printf("Response: %v\n", response.Status)
}
}
正确的示例。
错误信息传递出来。
package main
import (
"fmt"
"net/http"
)
func main() {
type Result struct { // <1>
Error error
Response *http.Response
}
checkStatus := func(done <-chan interface{}, urls ...string) <-chan Result { // <2>
results := make(chan Result)
go func() {
defer close(results)
for _, url := range urls {
var result Result
resp, err := http.Get(url)
result = Result{Error: err, Response: resp} // <3>
select {
case <-done:
return
case results <- result: // <4>
}
}
}()
return results
}
done := make(chan interface{})
defer close(done)
urls := []string{"https://www.google.com", "https://badhost"}
for result := range checkStatus(done, urls...) {
if result.Error != nil { // <5>
fmt.Printf("error: %v", result.Error)
continue
}
fmt.Printf("Response: %v\n", result.Response.Status)
}
}
出现三个错误时,退出。
package main
import (
"fmt"
"net/http"
)
func main() {
type Result struct { // <1>
Error error
Response *http.Response
}
checkStatus := func(done <-chan interface{}, urls ...string) <-chan Result { // <2>
results := make(chan Result)
go func() {
defer close(results)
for _, url := range urls {
var result Result
resp, err := http.Get(url)
result = Result{Error: err, Response: resp} // <3>
select {
case <-done:
return
case results <- result: // <4>
}
}
}()
return results
}
done := make(chan interface{})
defer close(done)
errCount := 0
urls := []string{"a", "https://www.google.com", "b", "c", "d"}
for result := range checkStatus(done, urls...) {
if result.Error != nil {
fmt.Printf("error: %v\n", result.Error)
errCount++
if errCount >= 3 {
fmt.Println("Too many errors, breaking!")
break
}
continue
}
fmt.Printf("Response: %v\n", result.Response.Status)
}
}
pipeline
pipeline用于流式处理或批处理数据时的强大工具。stage操作只是将数据输入,对其进行转换并将数据送回的操作。可以相互独立修改各个stage,混合搭配stage。
package main
import (
"fmt"
)
func main() {
multiply := func(value, multiplier int) int {
return value * multiplier
}
add := func(value, additive int) int {
return value + additive
}
ints := []int{1, 2, 3, 4}
for _, v := range ints {
fmt.Println(multiply(add(multiply(v, 2), 1), 2))
}
for _, v := range ints {
fmt.Println(2*(v*2+1))
}
}
pipeline stage属性
- 输入输出类型相同
- go函数实化stage并传递参数
package main
import (
"fmt"
)
func main() {
multiply := func(values []int, multiplier int) []int {
multipliedValues := make([]int, len(values))
for i, v := range values {
multipliedValues[i] = v * multiplier
}
return multipliedValues
}
add := func(values []int, additive int) []int {
addedValues := make([]int, len(values))
for i, v := range values {
addedValues[i] = v + additive
}
return addedValues
}
ints := []int{1, 2, 3, 4}
for _, v := range add(multiply(ints, 2), 1) {
fmt.Println(v)
}
for _, v := range multiply(add(multiply(ints, 2), 1), 2) {
fmt.Println(v)
}
}
构建pipeline的最佳实践
channel可以接收并返回值,可以安全地并行使用,可以被range遍历,可以实化。
生成器,将一组离散值转换为一个channel上数据流的函数。
package main
import (
"fmt"
)
func main() {
generator := func(done <-chan interface{}, integers ...int) <-chan int {
intStream := make(chan int)
go func() {
defer close(intStream)
for _, i := range integers {
select {
case <-done:
return
case intStream <- i:
}
}
}()
return intStream
}
multiply := func(
done <-chan interface{},
intStream <-chan int,
multiplier int,
) <-chan int {
multipliedStream := make(chan int)
go func() {
defer close(multipliedStream)
for i := range intStream {
select {
case <-done:
return
case multipliedStream <- i * multiplier:
}
}
}()
return multipliedStream
}
add := func(
done <-chan interface{},
intStream <-chan int,
additive int,
) <-chan int {
addedStream := make(chan int)
go func() {
defer close(addedStream)
for i := range intStream {
select {
case <-done:
return
case addedStream <- i + additive:
}
}
}()
return addedStream
}
done := make(chan interface{})
defer close(done)
intStream := generator(done, 1, 2, 3, 4)
pipeline := multiply(done, add(done, multiply(done, intStream, 2), 1), 2)
for v := range pipeline {
fmt.Println(v)
}
}
一些便利的生成器
重复遍历
package main
import (
"fmt"
)
func main() {
repeat := func(
done <-chan interface{},
values ...interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {//重复遍历values,发送给valueStream
for _, v := range values {
select {
case <-done:
return
case valueStream <- v:
}
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {//从valueStream中取出前num个值,发送给takeStream
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
done := make(chan interface{})
defer close(done)
for num := range take(done, repeat(done, 1), 10) {
fmt.Printf("%v ", num)
}
}
//1 1 1 1 1 1 1 1 1 1
生成随机数
package main
import (
"fmt"
"math/rand"
)
func main() {
repeatFn := func(
done <-chan interface{},
fn func() interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
select {
case <-done:
return
case valueStream <- fn():
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
done := make(chan interface{})
defer close(done)
rand := func() interface{} {return rand.Int()}
for num := range take(done, repeatFn(done, rand), 10) {
fmt.Println(num)
}
}
interface{}转string
package main
import (
"fmt"
)
func main() {
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
repeat := func(
done <-chan interface{},
values ...interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
for _, v := range values {
select {
case <-done:
return
case valueStream <- v:
}
}
}
}()
return valueStream
}
toString := func(
done <-chan interface{},
valueStream <-chan interface{},
) <-chan string {
stringStream := make(chan string)
go func() {
defer close(stringStream)
for v := range valueStream {
select {
case <-done:
return
case stringStream <- v.(string):
}
}
}()
return stringStream
}
done := make(chan interface{})
defer close(done)
var message string
for token := range toString(done, take(done, repeat(done, "I", "am."), 5)) {
message += token
}
fmt.Printf("message: %s...", message)
}
//Iam.Iam.I...
基准测试
func BenchmarkGeneric(b *testing.B) {
repeat := func(
done <-chan interface{},
values ...interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
for _, v := range values {
select {
case <-done:
return
case valueStream <- v:
}
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
toString := func(
done <-chan interface{},
valueStream <-chan interface{},
) <-chan string {
stringStream := make(chan string)
go func() {
defer close(stringStream)
for v := range valueStream {
select {
case <-done:
return
case stringStream <- v.(string):
}
}
}()
return stringStream
}
done := make(chan interface{})
defer close(done)
b.ResetTimer()
for range toString(done, take(done, repeat(done, "a"), b.N)) {
}
}
func BenchmarkTyped(b *testing.B) {
repeat := func(done <-chan interface{}, values ...string) <-chan string {
valueStream := make(chan string)
go func() {
defer close(valueStream)
for {
for _, v := range values {
select {
case <-done:
return
case valueStream <- v:
}
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan string,
num int,
) <-chan string {
takeStream := make(chan string)
go func() {
defer close(takeStream)
for i := num; i > 0 || i == -1; {
if i != -1 {
i--
}
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
done := make(chan interface{})
defer close(done)
b.ResetTimer()
for range take(done, repeat(done, "a"), b.N) {
}
}
扇入,扇出
扇出,扇入模式:在多个goroutine上重用我们的pipeline的单个stage以试图并行化来自上游stage的pull。
扇出,描述启动多个goroutine处理pipeline输入的过程。
扇入,描述将多个结果组合到一个channel的过程。
满足条件:
- 不依赖于之前stage计算的值(循环独立性)。
- 运行很长时间。
扇入意味着将多个数据流复用或合并成一个流。
串行处理流。
package main
import (
"fmt"
"math"
"math/rand"
"time"
)
func IsPrimeByRangeEnum(n int) bool {
if n < 2 {
return false
}
for i := int(2); i < n; i++ {
if n%i == 0 {
return false
}
}
return true
}
func IsPrimeBySqrtRangeEnum(n int) bool {
if n < 2 {
return false
}
for i, maxI := int(2), int(math.Sqrt(float64(n))); i <= maxI; i++ {
if n%i == 0 {
return false
}
}
return true
}
func main() {
repeatFn := func(
done <-chan interface{},
fn func() interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
select {
case <-done:
return
case valueStream <- fn():
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
toInt := func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int {
intStream := make(chan int)
go func() {
defer close(intStream)
for v := range valueStream {
select {
case <-done:
return
case intStream <- v.(int):
}
}
}()
return intStream
}
primeFinder := func(done <-chan interface{}, intStream <-chan int) <-chan interface{} {
primeStream := make(chan interface{})
go func() {
defer close(primeStream)
for integer := range intStream {
//if IsPrimeBySqrtRangeEnum(integer) {
if IsPrimeByRangeEnum(integer) {
select {
case <-done:
return
case primeStream <- integer:
}
}
}
}()
return primeStream
}
rand := func() interface{} { return rand.Intn(50000000) }
done := make(chan interface{})
defer close(done)
start := time.Now()
randIntStream := toInt(done, repeatFn(done, rand))
fmt.Println("Primes:")
for prime := range take(done, primeFinder(done, randIntStream), 10) {
fmt.Printf("\t%d\n", prime)
}
fmt.Printf("Search took: %v", time.Since(start))
}
并发处理流。
package main
import (
"fmt"
"math"
"math/rand"
"runtime"
"sync"
"time"
)
func IsPrimeByRangeEnum(n int) bool {
if n < 2 {
return false
}
for i := int(2); i < n; i++ {
if n%i == 0 {
return false
}
}
return true
}
func IsPrimeBySqrtRangeEnum(n int) bool {
if n < 2 {
return false
}
for i, maxI := int(2), int(math.Sqrt(float64(n))); i <= maxI; i++ {
if n%i == 0 {
return false
}
}
return true
}
func main() {
repeatFn := func(
done <-chan interface{},
fn func() interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
select {
case <-done:
return
case valueStream <- fn():
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
toInt := func(done <-chan interface{}, valueStream <-chan interface{}) <-chan int {
intStream := make(chan int)
go func() {
defer close(intStream)
for v := range valueStream {
select {
case <-done:
return
case intStream <- v.(int):
}
}
}()
return intStream
}
primeFinder := func(done <-chan interface{}, intStream <-chan int) <-chan interface{} {
primeStream := make(chan interface{})
go func() {
defer close(primeStream)
for integer := range intStream {
//if IsPrimeBySqrtRangeEnum(integer) {
if IsPrimeByRangeEnum(integer) {
select {
case <-done:
return
case primeStream <- integer:
}
}
}
}()
return primeStream
}
fanIn := func(
done <-chan interface{},
channels ...<-chan interface{},
) <-chan interface{} { // <1>
var wg sync.WaitGroup // <2>
multiplexedStream := make(chan interface{})
multiplex := func(c <-chan interface{}) { // <3>
defer wg.Done()
for i := range c {
select {
case <-done:
return
case multiplexedStream <- i:
}
}
}
// Select from all the channels
wg.Add(len(channels)) // <4>
for _, c := range channels {
go multiplex(c)
}
// Wait for all the reads to complete
go func() { // <5>
wg.Wait()
close(multiplexedStream)
}()
return multiplexedStream
}
done := make(chan interface{})
defer close(done)
start := time.Now()
rand := func() interface{} { return rand.Intn(50000000) }
randIntStream := toInt(done, repeatFn(done, rand))
numFinders := runtime.NumCPU()
fmt.Printf("Spinning up %d prime finders.\n", numFinders)
finders := make([]<-chan interface{}, numFinders)
fmt.Println("Primes:")
for i := 0; i < numFinders; i++ {
finders[i] = primeFinder(done, randIntStream)
}
for prime := range take(done, fanIn(done, finders...), 10) {
fmt.Printf("\t%d\n", prime)
}
fmt.Printf("Search took: %v", time.Since(start))
}
or-done-channel
不知道正在读取的channel是否将被取消,select包装读操作。
orDone := func(done, c <-chan interface{}) <-chan interface{} {
valStream := make(chan interface{})
go func() {
defer close(valStream)
for {
select {
case <-done:
return
case v, ok := <-c:
if ok == false {
return
}
select {
case valStream <- v:
case <-done: //return
}
}
}
}()
return valStream
}
for val := range orDone(done, myChan) {
//val
}
tee-channel
分隔channel的值,发送到代码的两个独立区域。
tee := func(done <-chan interface{}, in <-chan interface{}) (_, _ <-chan interface{}) {
out1 := make(chan interface{})
out2 := make(chan interface{})
go func() {
defer close(out1)
defer close(out2)
for val := range orDone(done, in) {
var out1, out2 = out1, out2 //外部out赋值给内部out
for i := 0; i < 2; i++ {
select {
case <-done:
return
case out1<-val: //内部外部out指向相同channel
out1 = nil //内部out赋值,不会覆盖外部out
case out2<-val:
out2 = nil //nil<-val不会执行,保证out1和out2都写入val
}
}
}
}()
return out1, out2
}
done := make(chan interface{})
defer close(done)
out1, out2 := tee(done, take(done, repeat(done, 1, 2), 4))
for val1 := range out1 {
fmt.Printf("out1: %v, out2: %v\n", val1, <-out2)
}
桥接channel
桥接技术,将充满channnel的channel拆解为一个简单的channel。
package main
import (
"fmt"
)
func main() {
orDone := func(done, c <-chan interface{}) <-chan interface{} {
valStream := make(chan interface{})
go func() {
defer close(valStream)
for {
select {
case <-done:
return
case v, ok := <-c:
if ok == false {
return
}
select {
case valStream <- v:
case <-done:
}
}
}
}()
return valStream
}
bridge := func(
done <-chan interface{},
chanStream <-chan <-chan interface{},
) <-chan interface{} {
valStream := make(chan interface{}) // <1>
go func() {
defer close(valStream)
for { // <2>
var stream <-chan interface{}
select {
case maybeStream, ok := <-chanStream:
if ok == false {
return
}
stream = maybeStream
case <-done:
return
}
for val := range orDone(done, stream) { // <3>
select {
case valStream <- val:
case <-done:
}
}
}
}()
return valStream
}
genVals := func() <-chan <-chan interface{} {
chanStream := make(chan (<-chan interface{}))
go func() {
defer close(chanStream)
for i := 0; i < 10; i++ {
stream := make(chan interface{}, 1)
stream <- i
close(stream)
chanStream <- stream
}
}()
return chanStream
}
for v := range bridge(nil, genVals()) {
fmt.Printf("%v ", v)
}
}
队列排队
带缓存的channel就是一种队列。只要stage完成工作,它将结果存储到稍后其他stage可以获取的临时存储位置,这个stage不需要保存一份指向结果的引用。
队列几乎不会加速程序的总运行时间,它只会让程序的行为有所不同。
done := make(chan interface{})
defer close(done)
zeros := take(done, repeat(done, 0), 3)//获取3个0
short := sleep(done, 1*time.Second, zeros)//遍历zeros,每次耗时1s,总耗时(4+4+1)s,需等待long取值后才继续
long := sleep(done, 4*time.Second, short)//遍历short,每次耗时4s,总耗时(4+4+4+1)s,需等待short生成值后才继续
pipeline := long//遍历long
增加缓存后,缩短short运行时间,整个pipeline耗时未改变。
done := make(chan interface{})
defer close(done)
zeros := take(done, repeat(done, 0), 3)//获取3个0
short := sleep(done, 1*time.Second, zeros)
short = buffer(done, 2, short)//short增加缓存后,总耗时(1+1+1)s
long := sleep(done, 4*time.Second, short)//遍历short,每次耗时4s,总耗时(4+4+4+1)s,需等待short生成值后才继续
pipeline := long//遍历long
p := processRequest(done, acceptConnection(done, httpHandler))
procsssRequest stage会阻止acceptConnection stage,acceptConnection可能被完全拒绝。
引入队列并不能减少整个pipeline运行时间,但可以减少 acceptConnection阻塞时间,让这个stage继续工作。请求会经历滞后,但不会被拒绝。
队列用途在于解耦|分离stage,以便一个stage的运行时间不会影响另一个stage的运行时间。
排队可以提高系统整体性能的唯一适用情况:
- 一个stage批处理请求可以节省时间。
- 一个stage中产生的延迟会在系统中产生一个反馈回环。例如将stage输入缓存到内存比发送到硬盘更快。
缓冲写入队列与未缓冲写入队列的简单比较。
写入到内部排队到缓冲区,直到积累到足够块,然后写出,分块。频繁增加内存消耗费时,分块减少时间,速度更快。如果算法支持向后看或排序进行优化,排队也可以有帮助。
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"os"
"testing"
)
func BenchmarkUnbufferedWrite(b *testing.B) {
performWrite(b, tmpFileOrFatal())
}
func BenchmarkBufferedWrite(b *testing.B) {
bufferredFile := bufio.NewWriter(tmpFileOrFatal())
performWrite(b, bufio.NewWriter(bufferredFile))
}
func tmpFileOrFatal() *os.File {
file, err := ioutil.TempFile("", "tmp")
if err != nil {
log.Fatal("error: %v", err)
}
return file
}
func performWrite(b *testing.B, writer io.Writer) {
repeat := func(
done <-chan interface{},
values ...interface{},
) <-chan interface{} {
valueStream := make(chan interface{})
go func() {
defer close(valueStream)
for {
for _, v := range values {
select {
case <-done:
return
case valueStream <- v:
}
}
}
}()
return valueStream
}
take := func(
done <-chan interface{},
valueStream <-chan interface{},
num int,
) <-chan interface{} {
takeStream := make(chan interface{})
go func() {
defer close(takeStream)
for i := 0; i < num; i++ {
select {
case <-done:
return
case takeStream <- <-valueStream:
}
}
}()
return takeStream
}
done := make(chan interface{})
defer close(done)
b.ResetTimer()
for bt := range take(done, repeat(done, byte(0)), b.N) {
writer.Write([]byte{bt.(byte)})
}
}
负反馈循环,向下螺旋,死亡螺旋。pipeline的效率低于临界阈值,pipeline上游积累的输入,导致pipeline损失更多效率,恶性循环。
pipeline入口处引入队列,创建请求滞后为代价来打破反馈循环。
排队模式:
- pipeline入口处。
- 这个stage批量操作将会带来更高的效率。
利特尔法则,通过足够的取样,可以预测pipeline的需求率。
pipeline只会和最慢的stage一样快。
若pipeline发生混乱,将丢失队列中的所有请求,可以坚持队列大小为零,或者可以转移到一个持久队列,在需要时随时读取的地方保存。
context包
函数取消的三个方面:
- goroutine的父goroutine取消它。
- goroutine取消它的子goroutine。
- goroutine中的任何阻塞操作都必须是可抢占的,以便它可以被取消。
context包两个主要目的:
- 提供可以取消调用分支的API。
- 提供用于通过呼叫传输请求范围数据的数据包。
context包用于并发程序中的超时,取消,传递消息或系统故障等抢占操作。
context类似done channel,在整个系统中传递。
context接口上的方法,无法改变底层结构的状态。接收context的函数并不能取消它,调用堆栈上的函数无法被子goroutine取消。
若函数调用中试图取消它后面的函数,它调用其中一个函数并传递它的上下文,然后将返回的上下文传递给它的子元素;否则只传递给定的上下文。类似于done的传递。子goroutine中只使用context实例,不修改。
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
//在调用返回的cancel函数时,关闭其done channel。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
//机器时钟超过给定的最后期限时,关闭其done channel。
func WithDeadline(parent Context, deadline time.Time) (ctx Context, cancel CancelFunc)
//给定的超过时间后,关闭其done channel。
func WithTimeout(parent Context, timeout time.Duration) (ctx Context, cancel CancelFunc)
//创建上下文的空实例。
//空的上下文。
func Background() Context
//空的context,占位符。
func TODO() Context
func WithValue(parent Context, key, val interface{}) Context
type CancelFunc func()
type Context interface {
//若context工作的work被取消,返回deadline时间。无设定期限,返回ok==false。
Deadline() (deadline time.Time, ok bool)
//若context不能被取消,可能返回nil;若context工作的work被取消,返回关闭的channel。
Done() <-chan struct{}
//goroutine被取消,返回非nil。
Error() error
//无关联key返回nil
Value(key interface{}) interface{}
}
done channel示例
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
done := make(chan interface{})
defer close(done)
wg.Add(1)
go func() {
defer wg.Done()
if err := printGreeting(done); err != nil {
fmt.Printf("%v", err)
return
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := printFarewell(done); err != nil {
fmt.Printf("%v", err)
return
}
}()
wg.Wait()
}
func printGreeting(done <-chan interface{}) error {
greeting, err := genGreeting(done)
if err != nil {
return err
}
fmt.Printf("%s world!\n", greeting)
return nil
}
func printFarewell(done <-chan interface{}) error {
farewell, err := genFarewell(done)
if err != nil {
return err
}
fmt.Printf("%s world!\n", farewell)
return nil
}
func genGreeting(done <-chan interface{}) (string, error) {
switch locale, err := locale(done); {
case err != nil:
return "", err
case locale == "EN/US":
return "hello", nil
}
return "", fmt.Errorf("unsupported locale")
}
func genFarewell(done <-chan interface{}) (string, error) {
switch locale, err := locale(done); {
case err != nil:
return "", err
case locale == "EN/US":
return "goodbye", nil
}
return "", fmt.Errorf("unsupported locale")
}
func locale(done <-chan interface{}) (string, error) {
select {
case <-done:
return "", fmt.Errorf("canceled")
case <-time.After(5 * time.Second):
}
return "EN/US", nil
}
contex示例
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background()) // WithCancel包装,允许取消。
defer cancel()
wg.Add(1)
go func() {
defer wg.Done()
if err := printGreeting(ctx); err != nil {
fmt.Printf("cannot print greeting: %v\n", err)
cancel() // cancel可以连续调用,printGreeting不成功,printFarewell则不调用。
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := printFarewell(ctx); err != nil {
fmt.Printf("cannot print farewell: %v\n", err)
}
}()
wg.Wait()
}
func printGreeting(ctx context.Context) error {
greeting, err := genGreeting(ctx)
if err != nil {
return err
}
fmt.Printf("%s world!\n", greeting)
return nil
}
func printFarewell(ctx context.Context) error {
farewell, err := genFarewell(ctx)
if err != nil {
return err
}
fmt.Printf("%s world!\n", farewell)
return nil
}
func genGreeting(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // 1s后自动取消返回的context。
defer cancel()
switch locale, err := locale(ctx); {
case err != nil:
return "", err
case locale == "EN/US":
return "hello", nil
}
return "", fmt.Errorf("unsupported locale")
}
func genFarewell(ctx context.Context) (string, error) {
switch locale, err := locale(ctx); {
case err != nil:
return "", err
case locale == "EN/US":
return "goodbye", nil
}
return "", fmt.Errorf("unsupported locale")
}
func locale(ctx context.Context) (string, error) {
/*
context提供超时时间,且系统时钟超过截止时间,返回DeadlineExceeded。
它允许函数立即失败,不必等待实际的超时发生,需大概知道下级调用需要时间。
*/
/*
if deadline, ok := ctx.Deadline(); ok {
if deadline.Sub(time.Now().Add(1*time.Minute)) <= 0 {
return "", context.DeadlineExceeded
}
}
*/
select {
case <-ctx.Done():
return "", ctx.Err() // 返回context被取消原因
case <-time.After(1 * time.Minute):
}
return "EN/US", nil
}
context中存储数据和检索数据的示例。
限制条件:
- key可比,==和!=
- 返回值安全,多个goroutine可访问
func main() {
ProcessRequest("jane", "abc123")
}
func ProcessRequest(userID, authToken string) {
ctx := context.WithValue(context.Background(), "userID", userID)
ctx = context.WithValue(ctx, "authToken", authToken)
HandleResponse(ctx)
}
fnuc HandleResponse(ctx context.Context) {
fmt.Printf("handling response for %v (%v)",
ctx.Value("userID"), ctx.Value("authToken"))
}
type foo int
type bar int
m := make(map[interface{}] int)
m[foo(1)] = 1
m[bar(1)] = 1
fmt.Printf("%v", m)
//map[1:1, 1:2]
基础值相同,不同类型信息,map会区分它们。
type ctxKey int
const (
ctxUserID ctxKey = itoa
ctxAuthToken
)
func UserID(c contextt.Context) string {
return c.Value(ctxUserID).(string)
}
func AuthToken(c contextt.Context) string {
return c.Value(ctxAuthToken).(string)
}
func main() {
ProcessRequest("jane", "abc123")
}
func ProcessRequest(userID, authToken string) {
ctx := context.WithValue(context.Background(), ctxUserID, userID)
ctx = context.WithValue(ctx, ctxAuthToken, authToken)
HandleResponse(ctx)
}
fnuc HandleResponse(ctx context.Context) {
fmt.Printf("handling response for %v (%v)",
UserID(ctx), AuthToken(ctx))
}
context应仅将上下文值用于传输进程和请求的请求范围数据,API边界,而不是将可选参数传递给函数。
启发式建议:
- 数据应该通过进程或API边界。
若进程内存中生成数据,应该通过API边界传递数据。 - 数据应该是不可变的。
请求的内容不可变,内部存储内容可变。 - 数据应趋向简单类型。
若请求范围数据是为了传递进进程和API边界,那么如果另一方不需要导入复杂包,那么拉出这些数据就容易得多。 - 数据应该是数据,而不是类型与方法。
操作是逻辑的,属于消耗这些数据的东西。 - 数据应该用于修饰操作,而不是驱动操作。
若算法根据context包含或不包含的内容而有所不同,可能会跨越可选参数范围。
小结
结合Go语言并发原语形成模式,编写可维护并发代码。