go concurrent programming
1. demo with time.Sleep()
py java 多线程编程,多进程编程,问题:cost memory
web 2.0 用户级线程,绿程,轻量级线程,协程 java-netty
coroutine:内存占用小(2k),切换快
go only has -goroutine,convenient
主死随从
func asynPrint(){ time.Sleep(time.Second) fmt.Println("bobby") } // main coroutine func main(){ go asynPrint() // 主死随从 fmt.Println("main") time.Sleep(2*time.Second) }
purpose: println for 10s
func main(){ // 匿名函数启动goroutine go func(){ for{ time.Sleep(time.Second) fmt.Println("...") } }() time.Sleep(10*time.Second) }
2. multi-variable
simulate 100 coroutines:
for i:=0; i<100;i++{ go func(){ fmt.Println(i) }() }
the problem is each coroutine share the same variable i
that may lead to 100 100 100 100
// solution 1 for i := 0; i < 100; i++ { tmp := i go func() { fmt.Println(tmp) }() } time.Sleep(10 * time.Second) // solution 2, elegantly for i := 0; i < 100; i++ { go func(i int) { fmt.Println(i) }(i) // pass by value, copy } time.Sleep(10 * time.Second)
3. GMP scheduling

goroutine -> 调度器映射到 -> 固定的线程池
为了调度公平需要一把lock,影响性能

最小化lock,g1 g2 有排队机制,一旦sleep for-loop,g1 idle,g2 active
g2需要进行操作系统级别调用如写入:g1 就找别的线程了

在此过程中操作系统的线程不会频繁的切换,而是processor分配给线程
4. WaitGorup: 优雅的阻塞
func WgFunc(n int) { var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) // wg +1 go func(i int) { defer wg.Done() // wg -1 fmt.Println(i) }(i) } wg.Wait() // wait until wg == 0 } func main() { WgFunc(100) }
5. Mutex & Atomic
1. use lock to make "total += 1" atomic -> lock can also lock a piece of logic
import ( "fmt" "sync" ) var total int var wg sync.WaitGroup var lock sync.Mutex func add() { defer wg.Done() for i := 0; i < 100000; i++ { lock.Lock() total += 1 lock.Unlock() } } func sub() { defer wg.Done() for i := 0; i < 100000; i++ { lock.Lock() total -= 1 lock.Unlock() } } func main() { wg.Add(2) add() sub() wg.Wait() fmt.Println(total) }
2. use atomic.addInt32() -> higher performance
var total int32 var wg sync.WaitGroup //var lock sync.Mutex func add() { defer wg.Done() for i := 0; i < 100000; i++ { atomic.AddInt32(&total, 1) } } func sub() { defer wg.Done() for i := 0; i < 100000; i++ { atomic.AddInt32(&total, -1) } }
6. RWMutex
读读并发,读写串行,写写串行
写锁要向读锁请求 -> 写入用读锁(无法读了)-> 写锁释放读锁 -> 可以读了
var wg sync.WaitGroup var rw sync.RWMutex func main() { wg.Add(6) // 一共6个并发 go func() { // 模拟写操作5s一次 time.Sleep(5 * time.Second) defer wg.Done() rw.Lock() defer rw.Unlock() fmt.Println("writeeeeee") time.Sleep(5 * time.Second) }() for i := 0; i < 5; i++ { // 模拟5个并发的读,每个并发0.5s读一次 go func() { defer wg.Done() for { rw.RLock() fmt.Println("readdddd") time.Sleep(500 * time.Millisecond) rw.RUnlock() // 无法用defer因为用于return前但这个是一个for-loop } }() } wg.Wait() }
7. channel

1) demo:有缓存通道,发送到channel(会阻塞) 先于 从channel接受
var msg chan string msg = make(chan string, 1) msg <- "123" data := <-msg fmt.Println(data)
when capacity is 0: 无缓存通道,从channel接受(会阻塞) 先于 发送到channel
func main() { var msg chan string msg = make(chan string, 0) msg <- "123" // block data := <-msg // cannot reach -> deadlock,解决:当前进程已阻塞,需要另起一个goroutine继续执行操作 fmt.Println(data) }
happen-before 参考:https://segmentfault.com/a/1190000039729417#item-3-5
https://golang.design/go-questions/channel/happens-before/
func main() { var msg chan string msg = make(chan string, 0) go func(msg chan string) { data := <-msg // 先执行,当前goroutine阻塞 fmt.Println(data) }(msg) msg <- "123" // 后执行,receive后恢复goroutine time.Sleep(3 * time.Second) }
no buffer channel:适用于通知
buffer channel:消费者与生产者
1. 消息传递,过滤
2. broadcast
3. subscription
4. task allocation
6. result summary
7. control concurrency
8. sync & async
for range:
func main() { var msg chan string msg = make(chan string, 2) go func(msg chan string) { for data := range msg { fmt.Println(data) } }(msg) msg <- "1" msg <- "2" close(msg) //msg <- "3" // after closing the channel, cannot send data data := <-msg fmt.Println(data) // but can still receive data time.Sleep(3 * time.Second) }
package main import ( "fmt" ) func main() { ch := make(chan int, 3) // Sending data to the channel ch <- 1 ch <- 2 ch <- 3 // Closing the channel close(ch) // Receiving data from the closed channel fmt.Println(<-ch) // Output: 1 fmt.Println(<-ch) // Output: 2 fmt.Println(<-ch) // Output: 3 // Receiving from an empty closed channel fmt.Println(<-ch) // Output: 0 (zero value of int) // Uncommenting the following line will cause a panic // ch <- 4 }
bidirectional channel can convert to unidirectional channel
msg := make(chan int)
var send chan<- int = msg
var read <- chan int = msg
package main import ( "fmt" "sync" ) func producer(send chan<- int) { defer wg.Done() for i := 0; i < 10; i++ { send <- i } close(send) // A sigal to the consumer } func customer(read <-chan int) { defer wg.Done() for data := range read { fmt.Println(data) }
// If you don't close thesendchannel in theproducerfunction,
// theconsumer'srangeloop will continue to wait for more values to be sent on the channel.
// Since no more values will be sent and the channel is not closed, theconsumerwill block indefinitely, } var wg sync.WaitGroup func main() { msg := make(chan int) wg.Add(2) go producer(msg) // 直接传bidirectional,可以自动转化 go customer(msg) wg.Wait() }
application: interweavingly print number and letter
package main import ( "fmt" "time" ) var letter, number = make(chan bool), make(chan bool) func PrintNum() { i := 0 for { <-letter // wait until letter send me the true to start fmt.Printf("%d%d", i, i+1) i = i + 2 number <- true // notify number that should print the letter } } func PrintLetter() { i := 0 str := "ASDFGHJKLxcvbn" for { <-number if i == len(str) { return } fmt.Print(str[i : i+2]) i = i + 2 letter <- true } } func main() { go PrintNum() // <- letter,blocked go PrintLetter() // <- number,blocked letter <- true // <- letter start to work time.Sleep(2 * time.Second) }

使用mutex lock和全局变量实现:for循环不断monitor全局变量,效率低下
var done bool var lock sync.Mutex func g1() { time.Sleep(time.Second) lock.Lock() defer lock.Unlock() done = true } func g2() { time.Sleep(2 * time.Second) lock.Lock() defer lock.Unlock() done = true } func main() { go g1() go g2() for { if done { fmt.Println("done") break } time.Sleep(500 * time.Millisecond) } }
use a single channel to monitor
var done = make(chan struct{}) // channel is Thread safety, no need to mutex lock func g1() { time.Sleep(time.Second) done <- struct{}{} } func g2() { time.Sleep(2 * time.Second) done <- struct{}{} } func main() { go g1() go g2() <-done fmt.Println("done") }
use multi channels: select
if two are ready simutaneously, the choice is random : to prevent hungry
var cn1 = make(chan struct{}) // channel is Thread safety, no need to mutex lock var cn2 = make(chan struct{}) func g1() { time.Sleep(time.Second) cn1 <- struct{}{} } func g2() { time.Sleep(2 * time.Second) cn2 <- struct{}{} } func main() { go g1() go g2() // <- cn1 万一g1时间很长,一直阻塞,而需求是最短的时间 // <- cn2 select { // select which one is ready; case <-cn1: fmt.Println("1 done") case <-cn2: fmt.Println("2 done") } }
有个问题:如果 cn1 和 cn2 都阻塞,main需要退出: timer
timer := time.NewTimer(5 * time.Second) set a timer (has a channel) that will be ready in 5s
timer := time.NewTimer(5 * time.Second) go g1() go g2() for { select { // select which one is ready; case <-cn1: fmt.Println("1 done") case <-cn2: fmt.Println("2 done") case <-timer.C: // when timer is ready: timeout fmt.Println("timeout") return } }
8. context
requirement: monitor the cpu and can stop autoly
var wg sync.WaitGroup // sharing a variable, not recommended, need to add RWMutex var stop bool func cpuInfo() { defer wg.Done() for { if stop { return } time.Sleep(time.Second) fmt.Println("cpu INFO ...") } } func main() { wg.Add(1) go cpuInfo() time.Sleep(3 * time.Second) stop = true // to make it quit autoly wg.Wait() }
add RWmutex
// sharing a variable var stop bool var rw sync.RWMutex func cpuInfo() { defer wg.Done() for { rw.RLock() if stop { return } rw.RUnlock() time.Sleep(time.Second) fmt.Println("cpu INFO ...") } } func main() { wg.Add(1) go cpuInfo() time.Sleep(3 * time.Second) rw.Lock() stop = true rw.Unlock() wg.Wait() }
sharing a variable -> using channel
using input param to transfer is elegant than using global one
var wg sync.WaitGroup func cpuInfo(stopCn chan struct{}) { defer wg.Done() for { select { case <-stopCn: fmt.Println("stop ...") return default: time.Sleep(time.Second) fmt.Println("cpu INFO ...") } } } func main() { var stopCn = make(chan struct{}) wg.Add(1) go cpuInfo(stopCn) time.Sleep(3 * time.Second) stopCn <- struct{}{} wg.Wait() }
context makes it even more elegant
![]()
func cpuInfo(ctx context.Context) { // context is an interface, can be implemented, within other params defer wg.Done() for { select { case <-ctx.Done(): fmt.Println("stop ...") return default: time.Sleep(time.Second) fmt.Println("cpu INFO ...") } } } func main() { wg.Add(1) ctx, cancel := context.WithCancel(context.Background()) // WithCancel returns a copy of parent with a new Done channel go cpuInfo(ctx) time.Sleep(3 * time.Second) cancel() wg.Wait() }
contex的传递性: 调用ctx1的cancel() 会顺着继承关系分别调用儿子的cancel。全部会被调用到

func main() { wg.Add(1) ctx1, cancel1 := context.WithCancel(context.Background()) ctx2, _ := context.WithCancel(ctx1) go cpuInfo(ctx2) time.Sleep(3 * time.Second) cancel1() // 父亲可以停掉儿子的零花钱 wg.Wait() }
withvalue 方便链路追踪ID,打印日志来跟踪交易: fmt.Printf("traceid = %s\n", ctx.Value("traceId"))
wg.Add(1) //1 超时,手动关闭 //ctx1, cancel1 := context.WithCancel(context.Background()) //ctx2, _ := context.WithCancel(ctx1) //2 超时,自动关闭 ctx, _ := context.WithTimeout(context.Background(), 6*time.Second) //3 withdeadline 在时间点关闭 //4 withvalue valueCtx := context.WithValue(ctx, "traceId", "dfghtres134") // 依然是继承一个parentctx go cpuInfo(valueCtx) wg.Wait()
9. syn.Cond
https://ieevee.com/tech/2019/06/15/cond.html
多个Reader等待共享资源ready的场景
Wait()会自动释放c.L,并挂起调用者的goroutine。之后恢复执行,Wait()会在返回时对c.L加锁。
除非被Signal或者Broadcast唤醒,否则Wait()不会返回。
用者应该在循环中调用Wait
signal() 只能唤醒一个wait()
实现unlimited buffer channel:
package main import ( "fmt" "sync" "time" ) type InfChannel struct { in chan int // 输入channel out chan int // 输出channel cond *sync.Cond buffer []int // 缓存 bufLock sync.Mutex // 缓存的锁 closeFlag bool } func NewInfChannel(cap int) *InfChannel { ch := &InfChannel{ in: make(chan int, cap), out: make(chan int, cap), cond: sync.NewCond(&sync.Mutex{}), buffer: []int{}, bufLock: sync.Mutex{}, } go ch.inWorker() go ch.outWorker() return ch } func (ch *InfChannel) In(data int) { ch.in <- data } func (ch *InfChannel) Out() <-chan int { return ch.out } func (ch *InfChannel) Close() { close(ch.in) ch.closeFlag = true // 还需要触发一次cond避免其已经陷入wait无法获取close ch.cond.Signal() } func (ch *InfChannel) inWorker() { for data := range ch.in { ch.bufLock.Lock() ch.buffer = append(ch.buffer, data) ch.bufLock.Unlock() // 调用cond,说明buf中已经有数据 ch.cond.Signal() } } func (ch *InfChannel) outWorker() { for { ch.cond.L.Lock() // cond等待缓存非空,如果为空等待,如果为空,且发现通道以及关闭,那么关闭输出并且返回 for len(ch.buffer) == 0 { if ch.closeFlag { close(ch.out) return } ch.cond.Wait() } ch.out <- ch.buffer[0] ch.bufLock.Lock() ch.buffer = ch.buffer[1:] ch.bufLock.Unlock() ch.cond.L.Unlock() } }
10. CAS
https://zhuanlan.zhihu.com/p/56733484
11.
- IO 操作
- Channel 阻塞
- system call
- 运⾏较⻓时间: 调用 runtime.Gosched() // yield execution to other goroutines
12.
func (set *threadSafeSet) Iter() <-chan string { ch := make(chan string) go func() { set.mu.RLock() defer set.mu.RUnlock() for key := range set.s { ch <- key } close(ch) }() return ch }
iterChannel := mySet.Iter()
ch会立即返回,返回后处于阻塞状态,因为no buffer + ch <- key
一旦我们 for-range,等于开始接受<- ch,goroutine就会继续运行

浙公网安备 33010602011771号