公众号merlinsea
- sync包下的互斥锁
- 场景:当多个协程对共享变量执行写操作的时候就会发生资源竞争,需要用互斥锁。
- 特点:
- 互斥锁必须是全局的,即所有协程共用同一把互斥锁
- 在需要写出执行的代码中对共享变量的写操作加锁。
package main import ( "fmt" "sync" "time" ) // 需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中。 // 最后显示出来。要求使用goroutine完成 // 思路 // 1. 编写一个函数,来计算各个数的阶乘,并放入到 map中. // 2. 我们启动的协程多个,统计的将结果放入到 map中 // 3. map 应该做出一个全局的. var ( myMap = make(map[int]int, 10) //声明一个全局的互斥锁 //lock 是一个全局的互斥锁, //sync 是包: synchornized 同步 //Mutex : 是互斥 lock sync.Mutex ) // test 函数就是计算 n!, 让将这个结果放入到 myMap func test(n int) { res := 1 for i := 1; i <= n; i++ { res *= i } //这里我们将 res 放入到myMap //加锁 lock.Lock() myMap[n] = res //解锁 lock.Unlock() } func main() { // 我们这里开启多个协程完成这个任务[20个] for i := 1; i <= 20; i++ { go test(i) } //休眠10秒钟【第二个问题 】 time.Sleep(time.Second * 5) //这里我们输出结果,变量这个结果 lock.Lock() for i, v := range myMap { fmt.Printf("map[%d]=%d\n", i, v) } lock.Unlock() }
- 管程channel
- 管道是一个队列的数据结构,数据遵循了先进先出的原则
- 管道本身是线程安全的,即多个协程操作同一个管道的时候不需要对管道进行加锁。
- 管道是有类型的,即一个string类型的管道只能放string类型的数据
- 管道是引用数据类型,即必须make以后才能使用,make的时候需要指定管道的容量,这个容量是不会改变的,后续最多也只能向这个管道中放入指定容量的数据,多了会deadlock死锁。
- 管道在close关闭以后就不能向管道中写入数据但依旧可以从管道中读取数据
- 管道的遍历只能使用for-range遍历
- 在for-range遍历的时候如果管道没有关闭会出现deadlock死锁问题。
- 在for-range遍历的时候如果管道关闭了,则正常遍历结束。
- 因此遍历管道的注意事项: 1、关闭管道 2、 用for-range遍历
type Cat struct { Name string Age int } func main() { //定义一个存放任意数据类型的管道 3个数据 var allChan chan interface{} allChan = make(chan interface{}, 3) // 向管道中放入数据 allChan <- 10 allChan <- "tom jack" cat := Cat{"小花猫", 4} allChan <- cat //从管道中获取数据 <-allChan <-allChan newCat := <-allChan //从管道中取出的Cat是什么? fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat) //使用类型断言,将interface{}类型转为Cat类型 a := newCat.(Cat) fmt.Printf("newCat.Name=%v", a.Name) } func main() { intChan := make(chan int, 3) intChan <- 100 intChan <- 200 close(intChan) // close //这是不能够再写入数到channel //intChan<- 300 fmt.Println("okook~") //当管道关闭后,读取数据是可以的 n1 := <-intChan fmt.Println("n1=", n1) //遍历管道 intChan2 := make(chan int, 100) for i := 0; i < 100; i++ { intChan2 <- i * 2 //放入100个数据到管道 } //在遍历时,如果channel没有关闭,则会出现deadlock的错误 //在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历 close(intChan2) for v := range intChan2 { fmt.Println("v=", v) } }
- 管道实现多协程之间的通信设计
- 由于主线程推出以后,基于主线程环境创建的协程也会结束,因此必须有一个通信机制使得主线程在其他协程执行的时候阻塞。
//write Data func writeData(intChan chan int) { for i := 1; i <= 50; i++ { //放入数据 intChan <- i // fmt.Println("writeData ", i) } close(intChan) //关闭 } //read data func readData(intChan chan int, exitChan chan bool) { for { v, ok := <-intChan if !ok { break } time.Sleep(time.Second) fmt.Printf("readData 读到数据=%v\n", v) } exitChan <- true close(exitChan) } func main() { //创建两个管道 intChan := make(chan int, 10) exitChan := make(chan bool, 1) go writeData(intChan) go readData(intChan, exitChan) for { _, ok := <-exitChan if !ok { break } } }