线程安全队列的三个简单部分的问题

Question for threadsafe queue from three easy pieces

下面是书上给出的代码,用来说明一个问题: 当有 1 个生产者 Tp 和 2 个消费者 Tc1,Tc2 时,以下代码将不起作用。 原因是Tp在往队列中放入数据的时候,唤醒了Tc1,在Tc1运行之前,Tc2偷偷进来拿了唯一的元素,从而清空了队列。然后 Tc1 开始调用 get() ,由于队列为空,这将抛出异常。书中建议将“if(count ==0)”替换为“while(count == 0)”,这样我就可以确保 Tc1 的状态在运行前是需要的。

只有当 Tc2 真的有办法在 Tc1 之前潜入时,这对我才有意义。如果 Tp 调用通知唤醒 Tc1,那么 Tc1 可能会抢到锁,在这种情况下 Tc2 将无法潜入并做任何事情。如果 Tc1 确实抢锁失败而 Tc2 确实成功了,那么 Tc1 应该回到休眠状态,直到它再次收到通知。在那种情况下,它永远不会进入 get().

so my question is: Why would ever Tc2 could sneak in before Tc1 and do anything, if Tc1 grabs the lock?

cond_t cond;
2 mutex_t mutex;
3
4 void *producer(void *arg) {
5     int i;
6     for (i = 0; i < loops; i++) {
7         Pthread_mutex_lock(&mutex); // p1
8         if(count == 1) // p2
9             Pthread_cond_wait(&cond, &mutex); // p3
10        put(i); // p4
11        Pthread_cond_signal(&cond); // p5
12        Pthread_mutex_unlock(&mutex); // p6
13    }
14 }
15
16 void *consumer(void *arg) {
17     int i;
18     for (i = 0; i < loops; i++) {
19         Pthread_mutex_lock(&mutex); // c1
20         if(count == 0) // c2
21             Pthread_cond_wait(&cond, &mutex); // c3
22         int tmp = get(); // c4
23         Pthread_cond_signal(&cond); // c5
24         Pthread_mutex_unlock(&mutex); // c6
25         printf("%d\n", tmp);
26     }
27 }

graph given by the book](https://i.stack.imgur.com/h5ZA5.png)
![book

pthread_cond_wait 释放互斥量并在“一个原子操作”中进入休眠状态。但是,唤醒并重新获取互斥量确实 而不是 是自动发生的(在 pthread_cond_broadcast 可能唤醒多个线程的情况下,这应该如何工作?)。

pthread_cond_wait 保证在 return 成功后,互斥体应该已经被锁定并且应该由调用线程拥有。但是刚被唤醒的线程不能仅仅因为互斥量当前可能被其他线程锁定而重新进入睡眠状态。再考虑一个 pthread_cond_broadcast 的场景——多个线程被唤醒并竞争互斥量,但显然只有其中一个可以抢到它——其他线程会立即回到睡眠状态。那将完全破坏 pthread_cond_broadcast.

的目的

所以有可能Tc1被唤醒,但是它可以获取互斥锁之前Tc2偷偷进来抢了锁。在这种情况下,Tc1 必须等待 Tc2 解锁互斥锁,然后才能观察到一个空队列。

更新
当等待条件变量的线程被唤醒时,它会尝试获取互斥锁。 pthread_cond_wait 只有在线程拥有互斥量后才会 return!所以考虑这个场景(队列最初是空的):

  • Tc1:尝试弹出一个项目,但队列为空,因此它调用 pthread_cond_wait 并阻塞 条件变量
  • Tp:推送一个项目并向条件变量发出信号
  • Tc1: 唤醒,但尚未获取互斥量
  • Tc2:潜入并抢走互斥量
  • Tc1:现在再次阻塞(仍在 pthread_cond_wait 内),但这次 _ 在互斥锁上_

在这种情况下,Tc1 必须等待 Tc2 释放互斥量,但此时 Tc2 已经删除了最后一项,因此一旦 Tc1 returns from pthread_cond_wait,它会发现一个空队列。

这只是一种可能的情况,甚至有 Tc1 和 Tc2 甚至不必竞争互斥量的情况:

  • Tc1:尝试弹出一个项目,但队列为空,因此它调用 pthread_cond_wait 并阻塞 条件变量
  • Tp:推送一个项目并向条件变量发出信号
  • Tc2:偷偷弹出那个项目
  • Tc1:现在才醒来 - 发现一个空队列

但是这些种族并不是此实现的唯一问题。 pthread_cond_wait 可以有 spurious wakeups,这意味着线程可以唤醒,即使条件变量没有被通知:

Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

所以基本上你应该 总是 重新评估你在 pthread_cond_wait return 时等待的条件,如果条件不满足,你可能想再打pthread_cond_wait回去睡觉。