作者回复: 如果用 Java,代码看起来是这样的:
class Channel {
private final Lock lock = new ReentrantLock();
private Condition cond = lock.newCondition();
private final Queue queue = new Queue();
private int n;
public Channel(int cap) {
n = cap;
}
public void push(Object v) {
lock.lock();
try {
while (queue.size() == n) {
cond.await();
}
if (queue.size() == 0) {
cond.signalAll();
}
queue.push(v);
} finally {
lock.unlock();
}
}
public Object pop() {
lock.lock();
try {
while (queue.size() == 0) {
cond.await();
}
if (queue.size() == n) {
cond.signalAll();
}
return queue.pop();
} finally {
lock.unlock();
}
}
}
作者回复: 代码没有问题的。先 Push 还是先通知都可以,次序可以交换的。因为反正锁还没有释放,这里只是标记一下哪些执行体可以调度,并没有真正发生控制权的转移。而且就算转移了也没问题的,你可以留意下本文贴的条件变量的 Wait 函数实现,它获得控制权后下一句就是 mutex.Lock 去申请锁,而我们这里是 Push 后才调用 mutex.Unlock 释放锁的,所以 Broadcast 和 Push 的次序可以随意交换。
作者回复: 是这样,你可以看我文章中Wait的代码,在唤醒后第一件事情是lock,也就是请求锁,所以只有A线程unlock后,其他被唤醒的线程中的一个会得到锁往下走。
作者回复: 一个操作系统调度,一个用户态自己来调度
作者回复: 1、go和java的代码只是形似,实质不同,因为go里面的channel是协程的通讯设施,java版本的是线程的通讯设施,大相径庭;
2、我们本节提的同步,和同步io的同步,两个是完全不同含义的同步;
3、我们课程不太会讲资料已经相对多的某个细节,除非这个细节非常关键影响到全局的理解。
作者回复: 1、你是对的,用两个cond性能会更好。但是用一个也是可以正常工作的。
2、cond.signal 不是把锁释放了,是让等待在这个cond上的执行体改变状态(从挂起到可被调度),从而允许调度程序给它执行权。
3、对的
作者回复: 设计上,goroutine 应该自己去 recover 错误,而不是主进程来 recover。
作者回复: 向 channel.push 一个对象时,要考虑 channel 满了,这时会等待,这就是 cond.Wait 的逻辑
作者回复: 不一样,这样写最终用的是libevent的异步回调模式。没法用同步io模型。
作者回复: 次序可以交换的。因为反正锁还没有释放,这里只是标记一下哪些执行体可以调度
作者回复: 对的
作者回复: 👍
作者回复: 你说的是对的。我其实没想到什么情况下用signal,大部分情况下都是用 broadcast,包括本文中的例子。因为用 signal 意味着每次资源的使用都要通知,其实退化为信号量的 PV 操作了,这一定性能是变差的。
作者回复: Go语言里面有两个管道实现,一个io.Pipe,是用于goroutine之间的;一个是os.Pipe,是用于进程之间的。
作者回复: 管道合理的使用场景是什么?
作者回复: 当然是锁。如果这都保证不了,那它就不是锁了
作者回复: 获取锁失败只有一种可能就是mutex对象非法(比如为nil),那就抛出异常,本身也是属于异常安全的代码。
作者回复: 本质上是的,不过它有一些高级用法是常规队列没有的,比如用select去多个channel同时进行读写。