来源:未知 时间:2023-04-20 17:02 作者:小飞侠 阅读:次
[导读] 并发编程 Golang并发通过 goroutine 来实现。goroutine是由Go语言的运行时#xff08;runtime#xff09;调度完成的#xff0c;而线程是由操作系统调度完成。 goroutine 和 channel 是Go秉承的CSP(Communicating Seque...
并发编程
1. 协程(goroutine) Go中使用
1.1. 创建一个goroutine 下面的代码开启一个单独的 package main import ("fmt") func hello() { fmt.Println("hello") } func main() { go hello()//time.Sleep(time.Second) fmt.Println("main") } 如何将上述hello()函数执行呢?一种直接的方法,使用 1.2. sync.WaitGroup 使用sync.WaitGroup的难点在于确定goroutine是什么时候结束的,即在哪里调用
package mainimport ("fmt""time")func hello(i int) { fmt.Println("hello goroutine", i)}func main() {for i := 0; i < 100; i++ {go hello(i) time.Sleep(time.Millisecond)}} 如果一个
通常情况下,会使用 package mainimport ("fmt""sync")func hello(i int) {defer wg.Done() // goroutine将hello中任务执行完,计数器减一 fmt.Println("hello goroutine", i)}var wg sync.WaitGroupfunc main() {for i := 0; i < 100; i++ { wg.Add(1) // 每开启一个goroutine,计数器加一go hello(i)//time.Sleep(time.Millisecond) wg.Wait() // 等待计数器降为0,再继续执行}// 更多的业务场景是,多个goroutine和main之间的同步,上述代码只是为了模拟sleep同样的效果。} 2. channel 单纯的将函数并发执行是没有意义的,函数和函数之间需要交换数据才能体现并发的价值。我们可以使用共享内存的方式进行数据交换(通过共享内存实现通信),但是它会导致不同的 2.1. channel类型// 1. 声明var ch1 chan intvar ch2 chan bool// 2. 创建channelmake(chan int, 16)// 3. 将一个值发送到通道 ch <- 10// 4. 从一个通道中接收值 x := <- ch// 赋值给x<- ch// 忽略结果// 5. 关闭通道close(ch) 2.2. 优雅的操作channel
package mainimport "fmt"func main() { ch1 := make(chan int) ch2 := make(chan int)// 开启一个goroutine,把0-24之间的数发送到ch1go func() {for i := 0; i < 25; i++ { ch1 <- i}close(ch1)}()// 从ch1中取出数据,计算平方,放到ch2go func() {for { x, ok := <- ch1if ok { ch2 <- x * x}}close(ch2)}()for i := range ch2 { fmt.Println(i)}} 2.3. 单向通道有些通道可能只用于发送,或者只用于接收。单向通道多用于函数的参数中。 func f1(ch1 chan<- int) {}func f2(ch1 <-chan int){} 2.4. 通道注意事项关闭: 只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被GC回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要作的,但关闭通道不是必须的。 2.5. 生产者消费者模型案例开启两个协程,一个协程生产数据,另一个协程对数据进行处理,处理完再把数据发回去。 package mainimport ("fmt""sync")var wg sync.WaitGroupfunc producer(in chan<- int) {defer wg.Done()for i := 0; i < 10; i++ { in <- i * i}close(in)}func consumer(out <-chan int) {defer wg.Done()for { x, ok := <-outif !ok {break} fmt.Println("num: ", x)}}func main() { ch := make(chan int, 16) wg.Add(2)go producer(ch)go consumer(ch) wg.Wait() fmt.Println("主协程结束...")} 3. 锁3.1. 竞态竞态是指在多个goroutine按某些交错顺序执行时,程序无法给出正确的结果。它对于程序是致命的,因为它们潜伏在程序中,出现的频率也很低,有可能仅在高负载环境或者在特定的编译器、平台和架构时才会出现。发生竞态现象的必要条件是:两个goroutine并发读写同一个变量,并且至少其中一个goroutine是写入操作。
Golang中代码中加锁,一般体现在是对临界区加锁。
3.2. 互斥锁:sync.Mutex 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 3.3. 读写互斥锁:sync.RWMutex 互斥锁是完全互斥的,但是有很多实际的场景下是读多写少(数据库读写分离),当我们并发的去读取一个资源不涉及资源修改的时候,没必要加锁,这种情况下使用读写锁是更好的一种选择。读写锁在Go语言中使用 4. sync包其它应用4.1. sync.Once 在编程的中,我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道等。Golang中sync包提供了一个解决方案 // 如果要执行函数f,就需要搭配闭包来使用func (o *Once) Do(f func()) {} 4.2. sync.Map Go语言内置的map不是并发安全的,多个goroutine并发访问map时会发生错误(一般超过21个goroutine读写map时编译器就会报错)。可以使用互斥锁的方式访问临界资源(在操作map前加锁,操作完释放锁)。由于Golang针对map的使用场景较多,系统提供了开箱即用(不需要使用make初始化)的并发安全的map即 5. atomic原子性操作
package mainimport ("fmt""sync")var x intvar wg sync.WaitGroupfunc add() { x++ wg.Done()}func main() {for i := 0; i < 100000; i++ { wg.Add(1)go add()} wg.Wait() fmt.Println(x)} //输出 97357 package mainimport ("fmt""sync")var x intvar wg sync.WaitGroupvar lock sync.Mutexfunc add() {defer wg.Done()// 对临界区加锁 lock.Lock() x++ lock.Unlock()}func main() {for i := 0; i < 100000; i++ { wg.Add(1)go add()} wg.Wait() fmt.Println(x)} // 输出10000 package mainimport ("fmt""sync""sync/atomic")var x int64var wg sync.WaitGroupvar lock sync.Mutexfunc add() {defer wg.Done()//lock.Lock()//x++//lock.Unlock() atomic.AddInt64(&x, 1)}func main() {for i := 0; i < 100000; i++ { wg.Add(1)go add()} wg.Wait() fmt.Println(x)} 以上就是golang的并发读写变量报错之处理锁,通道,原子操作全部内容,感谢大家支持自学php网。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com