【发布时间】:2011-06-16 03:57:37
【问题描述】:
我在 pthread 池上工作,将有五个单独的线程和一个队列。所有五个线程都在竞争从队列中获取工作,我知道我需要执行锁定/解锁和等待/信号的基本想法。
但我不确定我应该拥有多少互斥量和条件变量。现在我只有一个 mutex 和 cond 变量,所有五个线程都会使用它。
【问题讨论】:
我在 pthread 池上工作,将有五个单独的线程和一个队列。所有五个线程都在竞争从队列中获取工作,我知道我需要执行锁定/解锁和等待/信号的基本想法。
但我不确定我应该拥有多少互斥量和条件变量。现在我只有一个 mutex 和 cond 变量,所有五个线程都会使用它。
【问题讨论】:
一个互斥体和至少一个条件变量。
一个互斥体,因为有一个“事物”(即一块内存)要同步访问:所有工作人员之间的共享状态和推送工作的线程。
一个条件变量,一个或多个线程需要等待的条件。至少你需要一个条件变量来等待新工作,这里的条件是:“还有更多事情要做吗?” (或相反:“工作队列是空的吗?”)。
一个更实质性的答案是互斥体和相关条件变量之间存在一对多关系,共享状态和互斥体之间存在一对一关系。根据您告诉我们的内容以及您正在学习的内容,我建议您在设计中仅使用一种共享状态。当您需要更多的一种状态时,我建议您寻找一些更高级别的概念(例如通道、期货/承诺)来建立抽象。
无论如何,不要对不同的互斥锁使用相同的条件变量。
【讨论】:
详细说明@Ivan的解决方案...
您可以使用计数信号量 + 原子操作代替互斥量 + 条件变量来创建非常高效的队列。
semaphore dequeue_sem = 0;
semaphore enqueue_sem = 37; // or however large you want to bound your queue
入队操作只是:
wait_for(enqueue_sem)
atomic_add_to_queue(element)
signal(dequeue_sem)
出队操作是:
wait_for(dequeue_sem)
element = atomic_remove_from_queue()
signal(enqueue_sem)
“atomic_add_to_queue”和“atomic_remove_from_queue”通常使用紧密循环中的原子比较和交换来实现。
除了对称性之外,这个公式还限制了队列的最大大小;如果一个线程在一个完整的队列上调用 enqueue() ,它将阻塞。对于多线程环境中的任何队列,这几乎肯定是您想要的。 (您的计算机内存有限;应尽可能避免无限制地使用它。)
如果您确实坚持使用互斥锁和条件变量,则需要两个条件,一个用于排队等待(和 deque 发送信号),另一个用于其他方式。条件分别表示“队列未满”和“队列未空”,入队/出队代码同样对称。
【讨论】:
我认为,如果您将队列组织为堆栈/链表,则可以通过互锁操作从队列中窃取工作而根本不加锁(它将需要信号量而不是条件变量来防止 cmets 中描述的问题到此答案)。
伪代码是这样的:
当然,在这种情况下,也应该通过 InterlockedCompareExchange 向队列添加工作并发送信号量。
【讨论】:
candidate == null,然后多个其他线程发出条件信号,则只有一个服务员会看到信号并唤醒。您必须将互斥锁与条件变量句点结合使用。