12 go 并发编程
1. 概述
//python java php 多线程编程,多用户编程,多线程和多进程存在的问题主要是耗费内存。
//内存 线程切换 web2.0 用户级线程 绿城 轻量级线程 协程
// 内存占用小(2k)、切换快,go 语言的协程, go语言诞生之后只有协程可用 goroutine 非常方便
package main
import (
"fmt"
"time"
)
func asyncPrint() {
fmt.Println("bobby")
}
func main() {
go asyncPrint()
fmt.Println("main")
time.Sleep(time.Second)
}
2.匿名函数启动go routine
func main() { // 匿名函数启动go routine go func() { for { fmt.Println("bobby") time.Sleep(time.Second) } }() fmt.Println("main") time.Sleep(10 * time.Second) }
3. for 循环和闭包
func main() { // 匿名函数启动go routine // 1、闭包 2、 for 循环的问题。 for 循环的时候每次这个变量会被重用。 // 由于每次for循环的时候,i变量会被重用,当我进行到第二轮的时候这个i就变了
// 主要原因是goroutine 是并行的执行的和for循环执行是异步的取的数字 可能重复
for i := 0; i < 100; i++ { fmt.Println("这里是", i) go func() { fmt.Println(i) }() } fmt.Println("main") time.Sleep(10 * time.Second) }
这里是 0 这里是 1.......... 这里是 66 66 .... 58 这里是 68..... 67 ..... 7 这里是 86 这里是 87 这里是 88
goroutine中的i
变量是闭包中的一个变量,它引用的是循环变量i
的当前值。由于goroutine是并发执行的,它们可能不会按照创建它们的顺序执行。因此,当goroutine执行时,循环可能已经完成了多次迭代,i
的值可能已经不是goroutine启动时的值了。
在这段代码中,所有的goroutine都会打印同一个值,即循环结束时i
的值,也就是100。这是因为在goroutine被调度执行时,它们捕获的是循环变量i
的最终值。
为了在goroutine中正确打印每次迭代的i
值,你需要确保每个goroutine捕获的是迭代时的i
值的一个副本。这可以通过传递i
给匿名函数来实现:
go
func main() {
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
}(i) // 将当前的i值作为参数传递给匿名函数
}
fmt.Println("main")
time.Sleep(10 * time.Second) // 等待所有goroutine执行完成
}
在这个修改后的代码中,每次迭代都会创建一个新的i
值的副本,并将其传递给匿名函数。这样,每个goroutine都会打印出它被创建时的i
值。由于goroutine的调度和执行可能仍然不是顺序的,所以打印的顺序可能仍然是随机的,但至少每个goroutine打印的值将是正确的。
4.sync.WaitGroup 的使用
package main
import (
"fmt"
"sync"
)
// 子的goroutine如何知道主的goroutine已经结束了,主的goroutine如何知道子的goroutine已经结束了
func main() {
var wg sync.WaitGroup
//我要监控多少个goroutine执行结束
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
defer wg.Done()
fmt.Println(i)
//wg.Done和Add是同时出现的
}(i)
}
//waitGroup主要用于goroutine的执行等待,Add方法要和Done方法配套
wg.Wait()
//time.Sleep(time.Second)
}
锁的竞争,由于锁的竞争导致值有多种情况
package main import ( "fmt" "sync" ) /* * 锁的竞争 */ var total int var wg sync.WaitGroup func add() { defer wg.Done() for i := 0; i < 1000000; i++ { total += 1 } } func del() { defer wg.Done() for j := 0; j < 1000000; j++ { total -= 1 } } func main() { wg.Add(1) go add() go del() wg.Wait() fmt.Println(total) }
package main import ( "fmt" "sync" ) /* * 锁的竞争 */ var total int var wg sync.WaitGroup var lock sync.Mutex func add() { defer wg.Done() for i := 0; i < 1000000; i++ { lock.Lock() total += 1 lock.Unlock() } } func del() { defer wg.Done() for j := 0; j < 1000000; j++ { lock.Lock() total -= 1 lock.Unlock() } } func main() { wg.Add(2) go add() go del() wg.Wait() fmt.Println(total) }
写锁可以锁定读锁不再执行
package main import ( "fmt" "sync" "time" ) /* * 锁的竞争 */ // 锁本质上是将并行的代码串行化了,使用lock肯定影响性能。 // 即使是设计锁,那么也应该尽量的保证执行。 // 我们有两组协程,其中一组负责写数据,另一住负责读数据,web系统中大部分的场景都是读多写少。 // 虽然有多个goroutine,但是仔细分析发现,读协程之间应该并发,读和写之间应该串行。读和读之间不应该并行 // 读写锁 func main() { var rwlock sync.RWMutex var wg sync.WaitGroup wg.Add(6) go func() { time.Sleep(time.Second) defer wg.Done() rwlock.Lock() // 加写锁 写锁会防止别的读锁 或者写锁获取 fmt.Println("write") time.Sleep(time.Second * 5) rwlock.Unlock() }() for i := 0; i < 5; i++ { go func() { defer wg.Done() for { rwlock.RLock() // 加读锁 读锁不会阻止别人读取 time.Sleep(500 * time.Millisecond) fmt.Println("read") rwlock.RUnlock() } }() } wg.Wait() }
func main() { var msg chan string msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞 msg <- "bobby" data := <-msg fmt.Println(data) }
package main import ( "fmt" "time" ) func main() { var msg chan int //msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞 msg = make(chan int, 2) // 无缓冲的channel 如果职位0 放进去值会阻塞 go func(msg chan int) { // go 有一种happen-before 机制可以保障 data := <-msg fmt.Println(data) data = <-msg fmt.Println(data) }(msg) msg <- 1 msg <- 2 // 容易出现dead-lock的情况 // waitgroup 少了done调用 // 无缓冲的channel 也容易出现dead-lock time.Sleep(time.Second) }
package main import ( "fmt" "time" ) func main() { var msg chan int //msg = make(chan string, 1) // channel的初始化值 如果值为零的话 你放值进去会阻塞 msg = make(chan int, 2) // 无缓冲的channel 如果职位0 放进去值会阻塞 go func(msg chan int) { // go 有一种happen-before 机制可以保障 for data := range msg { fmt.Println(data) } fmt.Println("all done") }(msg) msg <- 1 msg <- 2 close(msg) // 已经关闭的closed不能再存值了 可以继续取值 dt2 := <-msg fmt.Println(dt2) // 容易出现dead-lock的情况 // waitgroup 少了done调用 // 无缓冲的channel 也容易出现dead-lock time.Sleep(time.Second) }
func main() { /* 默认情况下 channel都是双向的 但是 我们经常channel作用参数进行传递,希望对方也是单项使用 比如只取数 不传入参数 */ //var ch1 chan string // 双向channel //var ch2 chan<- float64 // 单向channel只能写入float64数据 //var ch3 <-chan int // 单向channel 只能读取数据 var ch chan int = make(chan int, 5) var send chan<- int = ch var receive <-chan int = ch send <- 99 <-receive }
5.channel只读和只写示例。
生产者和消费者channel.
package main import ( "fmt" "time" ) // 生产者channel 只能写数据 func producer(pdch chan<- int) { for i := 0; i < 10; i++ { pdch <- i * i } close(pdch) } // 消费者channel 只能读数据 func consumer(cs <-chan int) { for c := range cs { fmt.Printf("%d \n\r", c) } } func main() { var ch chan int = make(chan int, 10) go producer(ch) go consumer(ch) time.Sleep(time.Second * 10) /* 默认情况下 channel都是双向的 但是 我们经常channel作用参数进行传递,希望对方也是单项使用 比如只取数 不传入参数 */ //var ch1 chan string // 双向channel //var ch2 chan<- float64 // 单向channel只能写入float64数据 //var ch3 <-chan int // 单向channel 只能读取数据 //var ch chan int = make(chan int, 5) //var send chan<- int = ch // 只能发送 //var receive <-chan int = ch // 只能接收 //send <- 99 ////<-send 会报错 //// Invalid operation: <-send (receive from the send-only type chan<- int) //<-receive }
6.channel相互等待通知示例。
package main import ( "fmt" "time" ) /* 利用 channel实现交叉打印数字和字母 一个goroutine 打印数字 一个goroutine 打印字母 互相通知 例如如下: 12AB34CD56EF78GH910IJ1112KL1314MN15160P1718QR1920ST2122UV2324WX2526YZ2728 */ var num, letter = make(chan bool), make(chan bool) func printNum() { i := 1 fmt.Println("----数字----") for { // 此处等待另一个goroutine 过来通知 <-num fmt.Printf("%d%d", i, i+1) i += 2 letter <- true } } func printAlph() { var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" j := 0 //fmt.Println("----字母----") for { // 此处等待另一个goroutine 过来通知 <-letter if j >= len(str) { return } fmt.Print(str[j : j+2]) j += 2 num <- true } } func main() { go printAlph() go printNum() num <- true time.Sleep(time.Second * 10) }
7.goroutine 执行完成后立马知道执行完毕。
import ( "fmt" "sync" "time" ) /* select 类似于switch case 语句,但是select的功能和我们操作Linux里面提供的io的select poll epoll select 主要用于channel */ var lock sync.Mutex var boo bool func g1() { lock.Lock() defer lock.Unlock() time.Sleep(time.Second) boo = true } func g2() { lock.Lock() defer lock.Unlock() time.Sleep(2 * time.Second) boo = true } func main() { go g1() go g2() for { if boo { fmt.Println("done") time.Sleep(10 * time.Millisecond) return } } }
var done = make(chan struct{}) // channel 是多线程安全的,channel要初始化 func g1() { time.Sleep(10 * time.Second) done <- struct{}{} } func g2() { time.Sleep(4 * time.Second) done <- struct{}{} } func main() { go g1() go g2() <-done fmt.Println("done") }
8.select 接收多个goroutine 的执行结果
func g1(ch1 chan struct{}) { time.Sleep(3 * time.Second) ch1 <- struct{}{} } func g2(ch2 chan struct{}) { time.Sleep(4 * time.Second) ch2 <- struct{}{} } func main() { g1Channel := make(chan struct{}) g2Channel := make(chan struct{}) go g1(g1Channel) go g2(g2Channel) // 我要监控多个channel任何一个有值都知道 // select { case <-g1Channel: fmt.Println("g1 done") case <-g2Channel: fmt.Println("g2 done") } }
时来天地皆同力,运去英雄不自由