GO学习笔记(10)
并发编程
1. goroutine: 在函数前加一个关键字go,就为这个函数创建了一个线程
package main import ( "fmt" "time" ) func hello(i int) { fmt.Println("i = ", i) time.Sleep(time.Second) } // 普通调用 func demo1() { start := time.Now().Unix() for i := 0; i < 10; i++ { hello(i) } time.Sleep(time.Second * 10) end := time.Now().Unix() fmt.Println(end - start) // 耗时20s, i顺序输出 } // 开启线程,并发调用 func demo2() { start := time.Now().Unix() for i := 0; i < 10; i++ { go hello(i) } time.Sleep(time.Second * 10) end := time.Now().Unix() fmt.Println(end - start) // 耗时10s, i每次输出顺序不同 } func hello1() { i := 0 for { i++ fmt.Println("hello1: i = ", i) time.Sleep(time.Second) } } // 当主线程退出,其他任务则不再执行 func demo3() { go hello1() i := 0 for { i++ fmt.Println("demo3: i = ", i) time.Sleep(time.Second) if i == 2 { break } } /** demo3: i = 1 hello1: i = 1 hello1: i = 2 demo3: i = 2 hello1: i = 3 */ }
2. runtime:
package main import ( "fmt" "runtime" "time" ) // runtime.Gosched() 让出CPU时间片,重新等待安排任务 func goSched() { go func(s string) { for i := 0; i < 2; i++ { fmt.Println(s) } }("world") // 主协程 for i := 0; i < 2; i++ { // 切一下,再次分配任务 runtime.Gosched() fmt.Println("hello") } } // runtime.Goexit() 退出当前协程 func goExit() { go func() { defer fmt.Println("A.defer") func() { defer fmt.Println("B.defer") // 结束协程 runtime.Goexit() defer fmt.Println("C.defer") fmt.Println("B") }() fmt.Println("A") }() for { } } // runtime.GOMAXPROCS 同时执行Go代码时使用OS线程数量 func a() { for i := 1; i < 10; i++ { fmt.Println("A:", i) } } func b() { for i := 1; i < 10; i++ { fmt.Println("B:", i) } } func goMaxProcs() { runtime.GOMAXPROCS(2) go a() go b() time.Sleep(time.Second) } func main() { goSched() goExit() goMaxProcs() }
3. channel
package main import "fmt" // 无缓冲通道 (同步通道) func demo4() { ch1 := make(chan int) ch1 <- 10 fmt.Println("发送完成:", ch1) // fatal error: all goroutines are asleep - deadlock! // 无缓冲的通道只有在有人接收值的时候才能发送值 } // 解决办法 func recv(c chan int) { x := <-c fmt.Printf("接受成功: %v, %T \n", x, x) // 接受成功: 101, int } func demo5() { c := make(chan int) go recv(c) c <- 101 fmt.Printf("发送完成:%T \n", c) // 发送完成:chan int } // 有缓冲的通道 func demo6() { c := make(chan int, 2) c <- 10 c <- 11 //c <- 12 // 因为通道的容量为2,所以在没有接受前,不能放第三个值 fmt.Printf("发送完成:%T \n", c) // 发送完成:chan int } // 关闭通道 close() func demo7() { c := make(chan int) go func() { for i := 0; i < 5; i++ { c <- i } close(c) }() for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("main结束") } // 判断通道是否关闭 func demo8() { // 在demo7中,我们使用 data, ok := <-c; 通道关闭后再取值ok=false // 但是通常推荐使用for...range 来判断; 通道关闭后会退出for range循环 c := make(chan int) go func() { for i := 0; i < 10; i++ { c <- i * i } close(c) }() for i := range c { // 通道关闭后会退出for range循环 fmt.Println(i) } } // 单向通道 func counter(out chan<- int) { // out 只能发送: chan<- int for i := 0; i < 10; i++ { out <- i } close(out) } func squarer(out chan<- int, in <-chan int) { // out 只能发送: chan<- int; in 只能接收: <-chan int for i := range in { out <- i * i } close(out) } func printer(in <-chan int) { // in 只能接收: <-chan int for i := range in { fmt.Println(i) } } func demo9() { ch1 := make(chan int) ch2 := make(chan int) go counter(ch1) go squarer(ch2, ch1) printer(ch2) } func main() { demo4() demo5() demo6() demo7() demo8() demo9() }
4. goroutine池: 生产者消费者模型, 可以有效控制goroutine数量
教程的例子很不错
package main import ( "fmt" "math/rand" ) type Job struct { // id Id int // 需要计算的随机数 RandNum int } type Result struct { // 这里必须传对象实例 job *Job // 求和 sum int } func main() { // 需要2个管道 // 1.job管道 jobChan := make(chan *Job, 128) // 2.结果管道 resultChan := make(chan *Result, 128) // 3.创建工作池 createPool(64, jobChan, resultChan) // 4.开个打印的协程 go func(resultChan chan *Result) { // 遍历结果管道打印 for result := range resultChan { fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id, result.job.RandNum, result.sum) } }(resultChan) var id int // 循环创建job,输入到管道 for { id++ // 生成随机数 r_num := rand.Int() job := &Job{ Id: id, RandNum: r_num, } jobChan <- job } } // 创建工作池 // 参数1:开几个协程 func createPool(num int, jobChan chan *Job, resultChan chan *Result) { // 根据开协程个数,去跑运行 for i := 0; i < num; i++ { go func(jobChan chan *Job, resultChan chan *Result) { // 执行运算 // 遍历job管道所有数据,进行相加 for job := range jobChan { // 随机数接过来 r_num := job.RandNum // 随机数每一位相加 // 定义返回值 var sum int for r_num != 0 { tmp := r_num % 10 sum += tmp r_num /= 10 } // 想要的结果是Result r := &Result{ job: job, sum: sum, } //运算结果扔到管道 resultChan <- r } }(jobChan, resultChan) } }
5. select 多路复用:
前面有讲到select可以做条件语句使用, 每一个case都是一个通信操作
其实它的功能就是同时响应多个通道的操作
// 同时相应多个通信 // 其中一个通信操作完成,就执行该case分支 func demo01() { c1 := make(chan int) c2 := make(chan int) go func() { time.Sleep(time.Second) c1 <- 1 }() go func() { time.Sleep(time.Second * 2) c2 <- 2 }() select { case r1 := <-c1: fmt.Println(r1) case r2 := <-c2: fmt.Println(r2) } }
// 多个通信同时完成,则随机选择一个执行
func demo02() {
c1 := make(chan int)
c2 := make(chan int)
go func() {
c1 <- 1
}()
go func() {
c2 <- 2
}()
select {
case r1 := <-c1:
fmt.Println(r1)
case r2 := <-c2:
fmt.Println(r2)
}
}
// select还可以用来判断管道是否存满
func demo03() {
c1 := make(chan int)
go func(c chan int) {
for {
select {
case c <- 1:
fmt.Println("c <- 1")
default:
fmt.Println("channel full")
}
time.Sleep(time.Second)
}
}(c1)
for r := range c1 {
fmt.Println("res:", r)
time.Sleep(time.Second)
}
}
6. mutex: 多个goroutine同时操作一个资源, 就会发生竞态问题
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
// 互斥锁
func add() { for i := 0; i < 5000; i++ { x = x + 1 } wg.Done() } func demo003() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(x) // 结果不是我们想要的 }
// go 使用互斥锁可以解决这个问题
func add1() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func demo04() {
wg.Add(2)
go add1()
go add1()
wg.Wait()
fmt.Println(x) // 结果是10000
}
// 读写互斥锁: 用于读多写少
// 读的时候,其他线程都能读,但是不能写; 写的时候其他线程不能读写
func write() {
rwlock.Lock() // 写锁
x = x+1
time.Sleep(time.Second)
rwlock.Unlock()
wg.Done()
}
func read() {
rwlock.Lock()// 读锁
time.Sleep(time.Microsecond)
rwlock.Unlock()
wg.Done()
}
func demo05() {
start := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}