《Go语言并发之道》学习笔记之第3章 Go语言并发组件

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

《Go语言并发之道》学习笔记之第3章 Go语言并发组件

goroutine

goroutine是一个并发函数(不一定并行),go关键字触发。

func main() {
	go sayHello()
}
func sayHello() {
	fmt.Println("hello")
}

匿名函数

go func() {
	fmt.Println("hello")
}()

函数变量

sayHello := func() {
	fmt.Println("hello")
}()
go sayHello()

goroutine不是OS线程,也不是绿色线程(语言运行时管理的线程),而是协程。协程是一种非抢占式的简单并发子goroutine(函数、闭包或方法),不能被中断,有多个point,允许暂停或重新进入。

Go语言运行时观察goroutine行为,阻塞时自动挂起,不被阻塞时恢复。

GO语言通过M:N调度器实现主机托管机制,将M个绿色线程映射到N个OS线程,然后在绿色线程上运行goroutine。

Go语言遵循fork-join并发模型。fork指程序任一节点可以同时运行子节点与父节点,join指某个时刻合并并发的执行分支。

sync.WaitGroup同步

var wg sync.WaitGroup
sayHello := func() {
	defer wg.Done()
	fmt.Println("hello")
}()
wg.Add(1)
go sayHello()

//join point,阻塞main goroutine,直至sayHello函数结束
wg.Wait()	

共享相同的地址空间

var wg sync.WaitGroup
salutation := "hello"
wg.Add(1)
go func() {
	defer wg.Done()
	
	//main goroutine和子goroutine共享相同的地址空间salutation 
	salutation = "welcome"
}()

//确保执行子goroutine中salutation = "welcome"
wg.Wait()

//最终输出welcome
fmt.Println(salutation)

salutation分配在栈stack上,for循环结束后不在使用

for _, salutation := range []string{"hello", "greetings", "good day"} {
	fmt.Println(salutation)
}
//输出
//hello
//greetings
//good day

只:=初始化变量一次,之后用=赋值,等价于

salutation := "hello"
fmt.Println(salutation)
salutation = "greetings"
fmt.Println(salutation)
salutation = "good day"
fmt.Println(salutation)

goroutine开始前,循环已退出,salutation转移到堆heap中,salutation最终指向"good day"。

var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println(salutation)
	}()
}
wg.Wait()
//输出
//good day
//good day
//good day

主main salutation中将salutation副本传递到子main salutation闭包中,各goroutine中salutation不相同。

var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"} {
	wg.Add(1)
	go func(salutation string) {
		defer wg.Done()
		fmt.Println(salutation)
	}(salutation) //main goroutine中salutation复制给子goroutine中salutation
}
wg.Wait()
//输出顺序不保证
//good day
//hello
//greetings

创建一个goroutine廉价,几千字节,不运行时,Go自动增缩存储堆栈的内存。

memConsumed := func() uint64 {
	runtime.GC()
	var s runtime.MemStats
	runtime.ReadMemStats(&s)
	return s.Sys
}

var c <-chan interface{}
var wg sync.WaitGroup
noop := func(){wg.Done();<-c}

const numGoroutines = 1e4
wg.Add(numGoroutines)
before := memConsumed()
for i :=0 ; i < numGoroutines; i++ {go noop()}
wg.Wait()
after:= memConsumed()
fmt.Printf("%.3fkb", float64(after-before)/numGroutines/1024)

当一个被托管的并发进程切换到另一个并发进程时,必须保存它的状态(上下文切换)。

OS系统线程昂贵,必须保存寄存器、查找表和内存映射等东西,便于快速成功切换回当前线程,且传入的线程加载相同信息。

软件中上下文切换相对廉价,软件定义的调度器可以选择性保存检索数据,持久化数据,如何持久化等。

基准测试goroutine的切换时间

func BenchmarkContextSwitch(b *testing.B) {
	var wg sync.WaitGroup
	begin := make(chan struct{})
	c := make(chan struct{})

	var token struct{}
	sender := func() {
		defer wg.Done()
		<-begin
		for i := 0; i < b.N; i++ {
			c <- begin
		}
	}
	receiver := func() {
		defer wg.Done()
		<-begin
		for i := 0; i < b.N; i++ {
			<-c
		}
	}
	wg.Add(2)
	go sender()
	go receiver()
	b.StartTimer()
	close(begin)
	wg.Wait()
}

sync包

sync包包含对低级别内存访问同步最有用的并发原语。

WaitGroup

WaitGroup用于等待并发操作完成。

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup

	wg.Add(1) // <1>
	go func() {
		defer wg.Done() // <2>
		fmt.Println("1st goroutine sleeping...")
		time.Sleep(1)
	}()

	wg.Add(1) // <1>
	go func() {
		defer wg.Done() // <2>
		fmt.Println("2nd goroutine sleeping...")
		time.Sleep(2)
	}()

	wg.Wait() // <3>
	fmt.Println("All goroutines complete.")
}
package main

import (
	"fmt"
	"sync"
)

func main() {
	hello := func(wg *sync.WaitGroup, id int) {
		defer wg.Done()
		fmt.Printf("Hello from %v!\n", id)
	}

	const numGreeters = 5
	var wg sync.WaitGroup
	wg.Add(numGreeters)
	for i := 0; i < numGreeters; i++ {
		go hello(&wg, i+1)
	}
	wg.Wait()
}

互斥锁和读写锁

Mutex互斥锁,保护临界区,用于独占访问共享资源。

var count int
var lock sync.Mutex

increment := func() {
	lock.Lock()
	defer lock.Unlock()

	count++
	fmt.Printf("Incrementing: %d\n", count)
}

decrement := func() {
	lock.Lock()
	defer lock.Unlock()

	count--
	fmt.Printf("Incrementing: %d\n", count)
}

var arithmetic sync.WaitGroup
for i := 0; i <= 5; i++ {
	arithmetic.Add(1)

	go func() {
		defer arithmetic.Done()
		increment()
	}
}

for i := 0; i <= 5; i++ {
	arithmetic.Add(1)

	go func() {
		defer arithmetic.Done()
		decrement()
	}
}

arithmetic.Wait()
fmt.Println("Arithmetic complete.")

sync.Locker接口定义。

type Locker interface {
	Lock()
	Unlock()
}

RLocker()用于返回一个实现了Lock()和Unlock()方法的Locker接口

func (rw *RWMutex) RLocker() Locker
producer := func(wg *sync.WaitGroup, l sync.Locker) {
	defer wg.Done()
	for i:=5;i>0;i--{
		l.Lock()
		defer l.Unlock()
		time.Sleep(1)
	}
}

observer := func(wg *sync.WaitGroup, l sync.Locker) {
	defer wg.Done()
	l.Lock()
	defer l.Unlock()
}

test := func(count int, mutex, rwMutex sync.Locker) time.Duration {
	var wg sync.WaitGroup
	wg.Add(count+1)
	beginTestTime := time.Now()
	go producer(&wg, mutex)
	
	for i:=count;i>0;i-- {
		go observer(&wg, rwMutex)
	}

	wg.Wait()
	return time.Since(beginTestTime)
}

/*
"text/tabwriter"
func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer
minwidth 最小单元长度
tabwidth tab字符的宽度
padding  计算单元宽度时会额外加上它
padchar  用于填充的ASCII字符,如果是'\t',则Writer会假设tabwidth作为输出中tab的宽度,且单元必然左对齐。
flags    格式化控制
*/
tw := tabwriter.NewWriter(os.Stdout, 0, 1, 2, ' ', 0)
defer tw.Flush()

var m sync.RWMutex
fmt.Fprintf(tw, "Readers\tRWMutext\tMutex\n")
for i:=0;i<20;i++ {
	count := int(math.Pow(2, float64(i)))
	fmt.Fprintf(
		tw,
		"%d\t%v\t%v\n",
		count,
		test(count, &m, m.RLocker()),
		test(count, &m, &m),
	)
}

cond

goroutine的集合点,等待或发布event。
让goroutine有效地等待,直到它发出信号并检查它的状态。

c := sync.NewCond(&sync.Mutex{})
c.L.Lock()
for conditionTrue() == false {
	c.Wait()
}
c.L.Unlock()

Wait 方法在调用之前一定要使用 L.Lock 持有该资源,否则会发生 panic 导致程序崩溃;
Signal 方法唤醒的 Goroutine 都是队列最前面、等待最久的 Goroutine;
Broadcast 虽然是广播通知全部等待的 Goroutine,但是真正被唤醒时也是按照一定顺序的。

c := sync.NewCond(&sync.Mutex{})
queue := make([]interface{}, 0, 10)

removeFromQueue := func(delay time.Duration) {
	time.Sleep(delay)
	c.L.Lock()
	queue = queue[1:]
	fmt.Println("Removed from queue")
	c.L.Unlock()
	c.Signal()	//通知最开始等待的goroutine
}

for i:=0;i<10;i++ {
	c.L.Lock()
	for len(queue) == 2 {	//queue最多两个
		c.Wait()
	}
	fmt.Println("Adding to queue")
	queue = append(queue, struct{}{})
	go removeFromQueue(1*time.Second)
	c.L.Unlock()
}

Signal通知goroutine阻塞的调用Wait,条件已经被触发。运行时内部维护一个FIFO列表,等待接收信号;Signal发现等待最长时间的goroutine并通知它,而Broadcast向所有等待的goroutine发送信号。

type Button struct {
	Clicked *sync.Cond
}
button := BUtton{Clicked: sync.NewCond(&sync.Mutex{})}

subscribe := func(c *sync.Cond, fn func()) {
	var goroutineRunning sync.WaitGroup
	goroutineRunning.Add(1)
	go func() {
		goroutineRunning.Done()
		c.L.Lock()
		defer c.L.Unlock()
		c.wait()
		fn()
	}()
	goroutineRunning.Wait()
}

var clickRegistered sync.WaitGroup
clickRegistered.Add(3)
subscribe(button.Clicked, func() {
	fmt.Println("Maximizing window.")
	clickRegistered.Done()
})
subscribe(button.Clicked, func() {
	fmt.Println("Displaying annoying dialog box!")
	clickRegistered.Done()
})
subscribe(button.Clicked, func() {
	fmt.Println("Mouse clicked.")
	clickRegistered.Done()
})

button.Clicked.Broadcast()

clickRegistered.Wait()

once

确保即使在不同的goroutine上,也只会调用一次Do方法处理传递进来的函数。

var count int

increment := func() {
	count++
}
var once sync.Once

var increments sync.WaitGroup
increments.Add(100)
for i:=0;i<100;i++ {
	go func() {
		defer increments.Done()
		once.Do(increment)
	}
}

increments.Wait()
fmt.Printf("Count is %d\n", count)
//Count is 1
var count int
increment := func() {count++}
decrement := func() {count--}

var once sync.Once
once.Do(increment)	//只执行第一次调用的函数
once.Do(decrement)
fmt.Printf("Count is %d\n", count)
//Count is 1

死锁

var onceA, onceB sync.Once
var initB func()

initA := func(){onceB.Do(initB)}
initB = func(){onceA.Do(initA)}
onceA.Do(initA)

Pool模式是一种创建和提供可供使用的固定数量实例或Pool实例的方法。用于约束创建昂贵的场景(如数据库连接),以便只创建固定数量的实例,而不确定数量的操作仍然可以请求访问这些场景。

Pool接口中,Get方法检查池中是否有可用实例,无则new出新实例,完成时,Put归还到池中。

myPool := &sync.Pool{
	New: func() interface() {
		fmt.Println("Creating new instance.")
		return struct{}{}
	},
}
myPool.Get()
instance := myPool.Get()
myPool.Put(instance)
myPool.Get()
/*
Creating new instance.
Creating new instance.
*/
var numCalcsCreated int
calcPool := &sync.Pool{
	New: func() interface() {
		numCalcsCreated += 1
		mem := make([]byte, 1024)
		return &mem
	},
}

calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())

const numWorkers = 1024*1024
var wg sync.WaitGroup
wg.Add(numWorkers)
for i:=numWorkers; i>0;i-- {
	go func() {
		defer wg.Done()
		mem := calcPool.Get().(*[]byte)
		defer calcPool.Put(mem)
	}()
}

wg.Wait()
fmt.Printf("%d calculators were cteated.", numCalcsCreated)
//8 calculators were cteated.

Pool尽可能快地将预先分配的对象缓存加载启动,节省消费者时间,高吞吐量网络服务中常见,服务器试图快速响应请求。

每个请求都打开新的连接。

func connectToService() interface{} {
	time.Sleep(1*time.Second)
	return struct{}{}
}

func startNetworkDaemon() *sync.WaitGroup {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		server, err := net.Listen("tcp", "localhost:8080")
		if err!=nil {
			log.Fatalf("cannot listen: %v", err)
		}
		defer server.Close()

		wg.Done()

		for {
			conn, err := server.Accept()
			if err!=nil {
				log.Printf("cannot accept connection: %v", err)
				continue
			}
			connectToService()
			fmt.Fprintln(conn, "")
			conn.Close()
			}
		}
	}()
	return &wg
}

基准测试

func init() {
	damonStarted := startNetworkDaemon()
	damonStarted.Wait()
}

func BenchmarkNetworkRequest(b *testing.B) {
	for i:=0;i<b.N;i++ {
		conn, err := net.Dial("tcp", "localhost:8080")
		if err!=nil {
			b.Fatalf("cnnnot dial host: %v", err)
		}
		if _, err := ioutil.ReadAll(conn); err!=nil {
			b.Fatalf("cnnnot read: %v", err)
		}
		conn.Close()
	}
}

使用Pool

func connectToService() interface{} {
	time.Sleep(1*time.Second)
	return struct{}{}
}

func warmServiceConnCache() *sync.Pool {
	p := &sync.Pool{
		New: connecToservice,
	}
	for i:=0;i<10;i++ {
		p.Put(p.New())
	}
	return p
}

func startNetworkDaemon() *sync.WaitGroup {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		connPool := warmServiceConnCache()
		server, err := net.Listen("tcp", "localhost:8080")
		if err!=nil {
			log.Fatalf("cannot listen: %v", err)
		}
		defer server.Close()

		wg.Done()

		
		for {
			conn, err := server.Accept()
			if err!=nil {
				log.Printf("cannot accept connection: %v", err)
				continue
			}
			svcConn := connPool.Get()
			fmt.Fprintln(conn, "")
			connPool.Put(svcConn)
			conn.Close()
			}
		}
	}()
	return &wg
}

channel

类似河流,channel充当信息传递的管道,值沿着channel传递。
声明和实例化。

var dataStream chan interface{}
dataStream = make(chan interface{})
//dataStream := make(chan interface{})

单向channel。

//只能读取
var dataStream <-chan interface{}
dataStream = make(<-chan interface{})

//只能发送
var dataStream chan<- interface{}
dataStream = make(chan<- interface{})

隐式将双向channel转换为单向channel。

var receiveChan <-chan interface{}
var sendChan chan<- interface{}
dataStream := make(chan interface{})

receiveChan = dataStream
sendChan = dataStream

int类型的chan变量。

intStram := make(chan int)

简单使用

stringStream := make(chan string)
go func() {
	stringStream <- "Hello channels!"
}
fmt.Println(<-stringStream)

写入只读的channel和读取只写的channel都是错误的。

writeStream := make(chan<- interface{})
readStream := make(<-chan interface{})

<-writeStram
readStream <- struct{}{}

channel是阻塞的,只有channel内的数据被消费后,新的数据才能写入,只有channel内存在数据时,才能读取。

永远等待,死锁。

stringStream := make(chan string)
go func() {
	return
	stringStream <- "Hello channels!" // <1>
}()
fmt.Println(<-stringStream) // <2>

<-返回两个值,第二个值表示channel是否有新数据写入或者channel是否关闭,只有存在数据或关闭才能读取数据,否则会阻塞。

stringStream := make(chan string)
go func() {
	close(stringStream)
	return
	stringStream <- "Hello channels!"
}()
salutation, ok := <-stringStream // <1>
fmt.Printf("(%v): %v", ok, salutation)

从已关闭channel读取数据。

	intStream := make(chan int)
	close(intStream)
	integer, ok := <-intStream // <1>
	fmt.Printf("(%v): %v", ok, integer)

for range遍历channel,在channel关闭时自动中断循环。


	intStream := make(chan int)
	go func() {
		defer close(intStream) // <1>
		for i := 1; i <= 5; i++ {
			intStream <- i
		}
	}()

	for integer := range intStream { // <2>
		fmt.Printf("%v ", integer)
	}

被关闭的channel可以被无数次读取。


	begin := make(chan interface{})
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			<-begin // <1>
			fmt.Printf("%v has begun\n", i)
		}(i)
	}

	fmt.Println("Unblocking goroutines...")
	close(begin) // <2>
	wg.Wait()

buffered channel,容量未满时,写入不会阻塞,存在数据时,读取不会阻塞。无缓冲channel只是0容量的缓冲channel。


	var stdoutBuff bytes.Buffer         // <1>
	defer stdoutBuff.WriteTo(os.Stdout) // <2>

	intStream := make(chan int, 4) // <3>
	go func() {
		defer close(intStream)
		defer fmt.Fprintln(&stdoutBuff, "Producer Done.")
		for i := 0; i < 5; i++ {
			fmt.Fprintf(&stdoutBuff, "Sending: %d\n", i)
			intStream <- i
		}
	}()

	for integer := range intStream {
		fmt.Fprintf(&stdoutBuff, "Received %v.\n", integer)
	}

nil channel,读取写入数据会阻塞,关闭会panic。

channel不同状态下操作的结果。

操作Channel状态结果
Readnil阻塞
打开且非空输出值,true
打开但空阻塞
关闭的默认值,false
只写编译错误
Writenil阻塞
打开但填满阻塞
打开且不满写入值
关闭的panic
只读编译错误
Closenilpanic
打开且非空关闭Channel;读取成功,直到通道耗尽,然后读取产生值的默认值
打开但空关闭Channel;读到默认值
关闭的panic
只读编译错误

	chanOwner := func() <-chan int {
		resultStream := make(chan int, 5) // <1>
		go func() {                       // <2>
			defer close(resultStream) // <3>
			for i := 0; i <= 5; i++ {
				resultStream <- i
			}
		}()
		return resultStream // <4>
	}

	resultStream := chanOwner()
	for result := range resultStream { // <5>
		fmt.Printf("Received: %d\n", result)
	}
	fmt.Println("Done receiving!")

select语句

channel是将goroutine连接在一起的黏合剂,select语句是将channel绑定在一起的黏合剂。select语句可以帮助安全地将channel与取消、超时、等待和默认值等概念结合在一起。

随机等待任一case中channel满足时,执行,并退出select。

var c1, c2 <-chan interface{}
var c3 chan<- interface{}
select {
case <-c1:
	//执行某些逻辑
case <-c2:
	//执行某些逻辑
case c3<-struct{}:
	//执行某些逻辑
}

channel关闭时,case可执行。

start := time.Now()
c := make(chan interface{})
go fun() {
	time.Sleep(5*time.Second)
	close(c)
}()

fmt.Println("Blocking on read...")
select {
case <-c:
	fmt.Printf("Unblocked %v later.\n", time.Since(start))
}
//可能输出
//Blocking on read...
//Unblocked 5.000170047s later.

多个channel同时可用,select大约平均分配。

c1 := make(chan interface{}); close(c1)
c2 := make(chan interface{}); close(c2)

var c1Count, c2Count int
for i := 0; i <= 1000; i++ {
	select {
	case <-c1:
		c1Count++
	case <-c2:
		c2Count++
	}
}
fmt.Printf("c1Count: %d\nc2Count: %d\n", c1Count, c2Count)

//可能输出
//c1Count: 505
//c1Count: 496

超时机制。

var c <-chan int
select {
case <-c:
	//永远不会执行,nil channel读取
case <-time.After(1*time.Second):
	fmt.Println("Timed out.")
}
//输出
//Timed out.

default,无可用channel时的默认语句。

start := time.Now()
var c1, c2 <-chan int
select {
case <-c1:
case <-c2:
default:
	fmt.Printf("In default after %v\n\n", time.Since(start))
}
//所有channel(case)阻塞,调用default
//可能输出
//In default after 1.421us

for-select循环运行goroutine等待另一个goroutine关闭channel的同时,继续执行自己的操作。

done := make(chan interface{})
go fun() {
	time.Sleep(5*time.Second)
	close(done)
}()

workCounter := 0
loop:
for {
	select {
	case <-done:
		break loop
	default:
	}
	workCounter++
	time.Sleep(1*time.Second)
}
fmt.Printf("Achieved %v cycles of work before signalled to stop.\n", workCounter)
//可能输出
//Achieved 5 cycles of work before signalled to stop.

空select语句,永远阻塞。

select{}

GOMAXPROCS控制

runtime.GOMAXPROCS控制的OS线程的数量将承载“工作队列”,与主机逻辑处理器的数量有关。

runtime.GOMAXPROCS(runtime.NumCPU())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值