《Go语言并发之道》学习笔记之第4章 Go语言的并发模式

130 篇文章 4 订阅
31 篇文章 29 订阅

空接口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边界,而不是将可选参数传递给函数。
启发式建议:

  1. 数据应该通过进程或API边界。
    若进程内存中生成数据,应该通过API边界传递数据。
  2. 数据应该是不可变的。
    请求的内容不可变,内部存储内容可变。
  3. 数据应趋向简单类型。
    若请求范围数据是为了传递进进程和API边界,那么如果另一方不需要导入复杂包,那么拉出这些数据就容易得多。
  4. 数据应该是数据,而不是类型与方法。
    操作是逻辑的,属于消耗这些数据的东西。
  5. 数据应该用于修饰操作,而不是驱动操作。
    若算法根据context包含或不包含的内容而有所不同,可能会跨越可选参数范围。

小结

结合Go语言并发原语形成模式,编写可维护并发代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值