全国电子网站建设,建行购物网站,山东网站建设费用,石家庄网页设计临界资源安全问题
在并发编程中对临界资源的处理不当#xff0c;往往会导致数据的不一致问题
package mainimport (fmttime
)func main() {a : 1go func() {a 2fmt.Println(goroutine, a)}()a 3fmt.Println(a, a)time.Sl…临界资源安全问题
在并发编程中对临界资源的处理不当往往会导致数据的不一致问题
package mainimport (fmttime
)func main() {a : 1go func() {a 2fmt.Println(goroutine, a)}()a 3fmt.Println(a, a)time.Sleep(time.Second * 3)fmt.Println(a1, a)//结果//a 3//goroutine 2//a1 2
}
1.售票问题
火车票售票程序。共有10张票4个售票口同时出售,如何确保库存正常
package mainimport (fmttime
)var ticket int 10func main() {go sale(售票口1)go sale(售票口2)go sale(售票口3)go sale(售票口4)time.Sleep(time.Second * 3)//售票口2 当前剩余: 10//售票口3 当前剩余: 9//售票口4 当前剩余: 8//售票口1 当前剩余: 10//售票口4 当前剩余: 6//售票口1 当前剩余: 6//售票口2 当前剩余: 4//售票口3 当前剩余: 4//售票口2 当前剩余: 2//售票口3 当前剩余: 2//卖光了//售票口1 当前剩余: 2//卖光了//售票口4 当前剩余: 2//卖光了//售票口2 当前剩余: -2//卖光了
}func sale(name string) {for {if ticket 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, 当前剩余:, ticket)ticket--} else {fmt.Println(卖光了)break}}
}多个线程争抢时会出现问题。
2.mutex锁
sync 包提供了对互斥锁Mutex的支持用于实现多个 goroutines 之间的互斥访问。互斥锁是一种同步原语可以确保在任何时刻只有一个 goroutine 能够访问共享资源。
package mainimport (fmtsynctime
)var ticket int 10
var mutex sync.Mutex{}func main() {go sale(售票口1)go sale(售票口2)go sale(售票口3)go sale(售票口4)time.Sleep(time.Second * 8)//售票口1 当前剩余: 10//售票口1 当前剩余: 9//售票口4 当前剩余: 8//售票口2 当前剩余: 7//售票口3 当前剩余: 6//售票口1 当前剩余: 5//售票口4 当前剩余: 4//售票口2 当前剩余: 3//售票口3 当前剩余: 2//售票口1 当前剩余: 1//卖光了//卖光了//卖光了//卖光了
}func sale(name string) {for {mutex.Lock()if ticket 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, 当前剩余:, ticket)ticket--} else {mutex.Unlock()fmt.Println(卖光了)break}mutex.Unlock()}
}
但是实际上在GO语言的并发编程中有一句经单的话不要以共享内存的方式去通信而要以通信的方式去共享内存。 在GO语言中并不鼓励用锁的机制来保护共享状态在不同的Goroutine中分享信息以共享内存来通信。而是鼓励通过channel将共享状态或共享状态的变化在各个Goroutine中之间传递以通信的方式共享内存。这样同样能像锁一样保证同一时间只有一个Goroutine能访问共享状态。
3. WaitGroup
在上面的例子中我们通过time.sleep()来让主线程等待。这个时间我们不能精准控制。而sync.WaitGroup通常缩写为 wg是一种用于等待一组 goroutines 完成执行的同步原语。WaitGroup 通过一个计数器来实现等待计数器的初始值为 0。每当启动一个新的 goroutine 时计数器就会递增。当 goroutine 完成时就会调用 Done 方法将计数器递减。主程序可以调用 Wait 方法来阻塞直到计数器减至零表示所有的 goroutines 都已经执行完成。
package mainimport (fmtsynctime
)var ticket int 10
var mutex sync.Mutex{}
var wg sync.WaitGroupfunc main() {wg.Add(4)go sale(售票口1)go sale(售票口2)go sale(售票口3)go sale(售票口4)wg.Wait()//售票口1 当前剩余: 10//售票口1 当前剩余: 9//售票口4 当前剩余: 8//售票口2 当前剩余: 7//售票口3 当前剩余: 6//售票口1 当前剩余: 5//售票口4 当前剩余: 4//售票口2 当前剩余: 3//售票口3 当前剩余: 2//售票口1 当前剩余: 1//卖光了//卖光了//卖光了//卖光了
}func sale(name string) {for {mutex.Lock()if ticket 0 {time.Sleep(time.Millisecond * 500)fmt.Println(name, 当前剩余:, ticket)ticket--} else {mutex.Unlock()wg.Done()fmt.Println(卖光了)break}mutex.Unlock()}
}4.channel
通道Channel是用于在 goroutines 之间进行通信的一种机制。通道提供了一种安全的数据传输方式确保数据在发送和接收的过程中不会被竞争条件破坏。通道的主要目的是协调不同 goroutines 之间的执行。
package mainimport fmtfunc main() {var ch chan boolch make(chan bool)go func() {for i : 0; i 10; i {fmt.Println(i)}ch - true}()data : -chfmt.Println(通道里的值, data)
}一个通道发送和接收数据默认是阻塞的。当一个数据被发送到通道时在发送语句中被阻塞直到另一个Goroutine从该通道读取数据。 相对地当从通道读取数据时读取被阻塞直到一个Goroutine将数据写入该通道。本身channel就是同步的意味着同一时间只能有一条goroutine来操作。 最后:通道是goroutine之间的连接所以通道的发送和接收必须处在不同的goroutine中。 这些通道的特性是帮助Goroutines有效地进行通信而无需像使用其他编程语言中非常常见的显式锁或条件变量。
死锁
如果创建了chan没有Goroutine来使用了则会出现死锁。 使用通道时要考虑的一一个重要因素是死锁。如果Goroutine在一个通道上发送数据那么预计其他的Goroutine应该接收数据。如果这种情况不发生那么程序将在运行时出现死锁。 类似地如果Goroutine正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据否则程序将会死锁。
售票问题的channel实现写法
package mainimport (fmtsynctime
)var ticket int 10
var wg sync.WaitGroupfunc main() {ticketCh : make(chan int)wg.Add(4)go sale(售票口1, ticketCh)go sale(售票口2, ticketCh)go sale(售票口3, ticketCh)go sale(售票口4, ticketCh)for {time.Sleep(time.Millisecond * 500)ticketCh - ticketif ticket 0 {break}}close(ticketCh)fmt.Println(ticket) //0wg.Wait()fmt.Println(ticket) //0
}func sale(name string, ticketCh chan int) {for {v, _ : -ticketChfmt.Println(name, 当前剩余:, v)if v 0 {wg.Done()break}ticket--}
}