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状态 | 结果 |
---|---|---|
Read | nil | 阻塞 |
打开且非空 | 输出值,true | |
打开但空 | 阻塞 | |
关闭的 | 默认值,false | |
只写 | 编译错误 | |
Write | nil | 阻塞 |
打开但填满 | 阻塞 | |
打开且不满 | 写入值 | |
关闭的 | panic | |
只读 | 编译错误 | |
Close | nil | panic |
打开且非空 | 关闭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())