1. goroutine
go关键字实现的最简单的并发, 注意程序会在main函数结束时退出程序,并不会等待其他goroutin结束。
package main import ( "fmt" "time" ) func Add(x, y int) { z := x + y fmt.Println(z) } func main() { x := 1 y := 1 for i:=0; i<10 ;i++ { go Add(x, y) } time.Sleep(4) }
2.并发通信
并发通信通过 共享数据 和 消息来实现
package main import ( "fmt" "runtime" "sync" ) var counter = 0 func Add(lock *sync.Mutex) { lock.Lock() counter += 1 lock.Unlock() } func main() { lock := &sync.Mutex{} for i:=0 ; i<10 ; i++ { go Add(lock) } for { lock.Lock() c := counter lock.Unlock() runtime.Gosched() // 让出cpu的时间片,程序遇到goshed就好让出cpu让给其他goroutine执行 if c >= 10{ break } } fmt.Println(counter) }
3. chanel
package main import "fmt" func Count(count *int, ch chan int) { *count = *count + 1 fmt.Println(*count) ch <- 1 // 想channel 中写入数据,在channel中的数据被读取之前,这个操作是阻塞的 } func main() { count := 0 chs := make([]chan int, 5) for i:=0;i<5;i++{ chs[i] = make(chan int) go Count(&count,chs[i]) } for _, ch := range chs{ <- ch // 从channel中读取数据,在数据被写入之前,这个操作是 } }
3.1 基本语法
// 声明 var chanName chan ElementType //在类型之前加了一个chan关键字 var ch chan int var m map[string] chan bool // 定义 ch = make(chan int) // 初始化一个int型的channel //channel 的读写 ch <- value // 将一个数据发送至channel, 写入数据会产生阻塞,知道其他grountine从channel中将数据读走 varlue = <-ch // 从channel读取数据,如果channel中没数据也会导致阻塞,知道有数据写入
package main import "fmt" func Counsumer(ch1, ch2 chan int) { var value int for { value = <-ch1 if value >= 100{ ch2 <- 0 } fmt.Println("消费了", value) } } func main() { ch1 := make(chan int) ch2 := make(chan int) go Counsumer(ch1, ch2) var i int = 0 for i < 100{ i += 1 ch1 <- i fmt.Println("生产了", i) } <- ch2 }
3.2 select
select 语法与switch相似,只不过每一个case条件必须是一个channel操作。
package main import "fmt" func main() { ch := make(chan int, 1) j := 0 for j < 100{ j += 1 select { case ch <- 0: case ch <- 1: } i := <-ch fmt.Println(i) } }
执行上面代码会发现打印的数字有时为1有时为0。这与select的机制有关,它是随机的。
3.3 缓冲机制
package main import "fmt" func Consumer(ch chan int) { for{ value := <-ch fmt.Println("消费了", value) } } func main() { ch := make(chan int, 100) // 构造channel的时候可以指定channel的缓存大小 i := 0 // 设置缓存大小后,如果chan没满则生产环节不再阻塞 go Consumer(ch) for i < 100{ i += 1 print("生产", i, " ") ch <- i } }
带缓冲区的channel还可以这样读
package main import ( "fmt" ) func Consumer(ch, ch2 chan int) { Loop1: for { //value := <-ch for value := range ch{ fmt.Println("消费了", value) if value >= 100{ break Loop1 } } } fmt.Println("lalala") ch2 <- 0 } func main() { ch := make(chan int, 100) ch2 := make(chan int) i := 0 go Consumer(ch, ch2) for i < 100{ i += 1 print("生产", i) ch <- i } <-ch2 }
3.4超时机制
package main import ( "fmt" "time" ) func main() { timeout := make(chan int, 1) func() { time.Sleep(1) timeout <- 1 }() ch := make(chan int, 1) ch <- 1 t := time.Now() select{ case <-ch: fmt.Println("从ch中读到数据") case <-timeout: fmt.Println("从timeout读取数据") } fmt.Println(time.Now().Sub(t)) }
3.5 channel的传递
chanel本身也是原生类型,与map之类的类型一样,channel定义之后也可以通过channel传递
package main type Pipe struct { value int handler func(i int) int next chan int } func handle(queue chan *Pipe) { for data := range queue{ data.next <- data.handler(data.value) } } func add(i int)int { return i + 1 } func main() { var p1 Pipe = Pipe{1, add, make(chan int,1)} }
3.6 单向channel
var ch1 chan int // 正常的channel var ch2 chan<- float64 // ch2是单向channel, 只能用于写float64数据 var ch3 <-chan int // 只能用于读取int数据 ch4 := make(chan int) ch5 := <-chan int(ch4) // 将ch4转换为单向读取channel ch6 := chan<- int(ch4) // 将ch4转换为单向写channel // 用法 func Parse(ch <-chan int) { for value := range ch{ // 在这个函数中只能对ch进行读操作 fmt.Println(value) } }
package main import ( "fmt" "time" ) func read(ch <-chan int) { value := <-ch fmt.Println(value) } func main() { ch4 := make(chan int, 1) go read(ch4) ch4 <- 1 time.Sleep(1e9) }
3.7 关闭channel
close(ch)
3.8 出让时间片
runtime.Gosched() // 如果要精细的控制goroutine的行为,就必须深入的了解runtime包提供的具体功能
3.9 同步
3.9.1同步锁:
sync.Mutex
sync.RWmutex 单写多读模型,一个goroutine获取读锁之后,只限制写,其他goroutine仍可以读。而写锁会组织所有goroutine获取读锁和写锁
Lock 写锁
RLock 读锁
var l sync.Mutex func foo() { l.Lock() defer l.Unlock() }
3.9.2全局唯一性操作
先来看一段代码
package main import "fmt" var done bool = false func setup() { fmt.Println("hello, world") done = true } func doprint() { if !done{ setup() } }
这段代码的目的是保证setup函数只被执行一次。但是细看还是会有问题,因为setup并不是一个原子性操作, 这种写法可能会导致setup函数被多次调用。