我听说当程序具有高并发性时,channel
比sycn.Mutex.Lock()
更好。但为什么渠道更有效率呢?在我看来,要实现一个安全的缓冲池(我认为通道可以被视为缓冲池(,必须使用锁。
如果channel
更有效,为什么会有sycn.Mutex
?因为我可以编写下面的代码来模拟sync.Mutex
。
type seme struct {
lock chan int
locked bool
}
func (l *seme)Lock() {
// state 0 for initial, 1 for locked, 2 for free.
if atomic.CompareAndSwapInt32(&l.state, 0, 1) {
l.lock = make(chan int, 1)
}
l.lock <- 0
l.state = 1
}
func (l *seme)UnLock() {
if !atomic.CompareAndSwapInt32(&l.state, 1, 2) {
panic("UnLock a free Lock")
}
l.state = 2
<- l.lock
}
如果channel
在任何地方都比mutex
好,为什么我要使用mutex
?也就是说,我什么时候应该使用mutex
而不是channel
?有人能给我举个例子吗?
通道与互斥锁有根本不同。
一个有足够细节的正确答案太长了,所以让我们只介绍主要亮点,特别是在Go频道方面:
Go通道在并发例程(goroutines(之间提供类型的数据传输sync.Mutex
为并发例程(goroutines(之间的共享内存提供互斥数据传输表示复制某种类型的值。Goroutine a将一个值放入通道:
var v T // v is a value of type T
...
ch <- v // put v's value into the channel
将v
放入通道块的尝试何时以及是否会发生,以及如果您愿意,可以对此做些什么,都会变得有点复杂,但如果通道是缓冲的,那么至少一些值可以立即进入,而不会有任何阻塞,这样发送goroutine就可以继续。如果通道未缓冲,则发送器阻塞,直到某个接收器goroutine正在积极等待值。(有时这是可取的,有时不是。(
同时,goroutine B从通道中取出一个值:
var w T // w is also a value of type T
...
w <- ch
或者只是:
w :=<- ch
同样,这何时以及是否会阻碍,你能做什么,什么时候应该做什么,等等,都可能变得复杂;但在简单的情况下,它等待有一个可用的值——让一些goroutine执行ch <- v
,或者如果通道被缓冲,则已经执行了——然后它将放入通道的值复制到变量w
中。变量v
可能已经改变,甚至在这一点上被完全破坏。值已安全存储在通道中,现在已从通道中删除并放入变量w
中。
Go通道具有一些附加功能,例如关闭通道的功能,可以防止在通道上进行进一步写入,并向读取操作发出"数据结束"通知。这可以通过单值读取(w, ok <- ch
(进行测试,并在for w := range ch
循环中进行隐式测试。
相比之下,sync.Mutex
实例只允许您调用Lock
和Unlock
。它不包含任何排队的值(就像缓冲通道一样(,甚至没有一个类型(除了sync.Mutex
本身(可以防止您意外地将float
发送到预期string
或其他类型。这个锁的存在允许两个或多个goroutine使用共享内存区域来完成一些事情。
通道的运行时实现很可能需要某种互斥。这不一定是sync.Mutex
本身:任何提供足够互斥的东西都足够了。在您可能正在使用的Go通道实现中,它不是sync.Mutex
,而是一个专门的运行时互斥体。(请注意,此链接指向特定的行,该行可能会随着时间的推移而过时。(由于某些通道代码是由编译器本身直接生成的,因此不应假设此处的运行时例程正在使用:您的编译器可能不同。然而,研究这个特定的实现可能会让你对如何使用通道有所启发。
互斥体通常比通道简单得多。要查看示例,请将上面通道实现中的代码量(不包括编译器的内联插入(与这个特定Go实现的sync.Mutex
源代码进行比较。
Golang中的并发程序有两种通信方式。
-
sync包:通过共享内存进行通信。
-
通道:通过交流共享记忆。
Go推荐
不要通过共享内存进行通信。相反,通过共享内存沟通。
哪些操作是原子操作?互斥锁呢?解决这个问题