为什么此互斥代码无法按预期工作

Why does this mutex code not work as expected?

本文关键字:工作 代码 为什么      更新时间:2023-10-16

关于这个主题的帖子和答案一堆乱七八糟,但似乎没有一个能完全模拟我的问题。在谷歌搜索和搜索堆栈溢出之后,我不太明白我问题的答案。

我有两个线程,一个主线程和一个从线程。从站需要等待主服务器才能超过某个点,所以我创建了一个互斥锁作为全局变量:

pthread_mutex_t lock;

然后在主线程的初始化中,早在从线程有机会访问它之前,我就锁定了它:

在主线程中初始化:

pthread_mutex_lock(&lock)

然后在奴隶中,当需要等待主人时,我这样做:

奴隶必须在这里等待:

pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

同时,回到主人,我有这个时间"释放"被阻止等待的奴隶:

pthread_mutex_unlock(&lock);
pthread_mutex_lock(&lock);

(注意:锁定/解锁的顺序与主节点相反。

根据我对互斥锁的理解,我认为奴隶会撞到锁上,然后卡在那里等待主人解锁它,然后立即再次锁定它。就时间而言,从站需要很长时间(保证)才能再次回到这里,因此主站将有很多时间重新锁定它。同样,主人在一段时间内不会再回到这里,我们不需要担心主人或奴隶将另一个打回这些检查站。

当它没有按预期工作时,我扔了一些 printf 来确认主设备解锁,然后在从站解锁之前重新锁定互斥锁。我的理解是,从站早在主站到达那里进行解锁和(重新)锁定之前就已经请求了锁,无论主站解锁和(重新)锁定之间的时间有多短,从站仍然应该能够锁定互斥锁,因为他已经"排队"等待。

但是,我看到的情况是,主设备解锁互斥锁,然后立即重新锁定它,即使从站已被耐心阻止等待锁定它的机会。

以下是包含 printf 的代码和生成的输出:

奴隶:

printf("slave thread starting lock sequencen");fflush(stdout);
pthread_mutex_lock(&lock);
printf("slave thread intra lock sequencen");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("slave thread completed lock sequencen");fflush(stdout);

主人:

printf("master thread starting lock sequencen");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("master thread intra lock sequencen");fflush(stdout);
pthread_mutex_lock(&lock);
printf("master thread completed lock sequencen");fflush(stdout);

现在这是我看到的输出:

从线程起始锁定序列

。然后一段时间过去(几秒钟),当从站被阻止时,最后出现:

主线程启动锁定顺序

主线程内锁序列

主线程完成锁定序列

与此同时,奴隶没有进一步的进展,他永远被封锁。我本以为他会阻止主人重新锁定,应该吐出他的印记,表明他已经向前移动了。此输出清楚地表明,被阻止的从站没有机会锁定互斥锁,即使他正在耐心地排队等待轮到他。

那么我对互斥体和锁定/解锁缺少什么?

-燃气轮机-

正如您问题的评论中所述,pthreads 互斥体不能保证公平性。

此作业的正确工具是共享标志变量,受互斥锁保护并使用条件变量等待:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int flag = 0;

从属等待旗帜:

pthread_mutex_lock(&lock);
while (!flag)
pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);

当主站想要释放从站时,它会设置标志并发出条件变量的信号:

pthread_mutex_lock(&lock);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);

(请注意,在pthread_cond_wait()内部阻止时互斥锁不会保持)。

当它没有按预期工作时,我扔了一些 printf 来确认主设备解锁,然后在从站解锁之前重新锁定互斥锁。我的理解是,从站早在主站到达那里进行解锁和(重新)锁定之前就已经请求了锁,无论主站解锁和(重新)锁定之间的时间有多短,从站仍然应该能够锁定互斥锁,因为他已经"排队"等待。

没有理由对线程公平,如果你虐待他们,他们不会提出工会申诉。但是有理由让整个系统尽快完成尽可能多的工作。停止一个线程以启动另一个线程会对性能产生重大负面影响,因为新线程启动时所有缓存都处于冷状态,并且当您切换回来时也会发生同样的事情。

你的工作是确保你的代码在执行时完成你想要它做的工作。调度程序将尝试尽快完成工作,只是偶尔点头以求公平。

也许您可以在信号量之后对问题进行建模,我发现这通常更容易理解和实现。 类似于以下伪代码示例。

//before create slave thread, create semaphore first
sem_t data_ready, slave_done;
sem_init(&data_ready, 0);
sem_init(&slave_done, 0);

在主线程中:

//master do something AAA
sem_post(data_ready);
//master do something BBB
sem_wait(slave_done);  //master may sleep here, and maybe interrupted by signal, need to handle that
//master do something CCC

在从属线程中:

//slave do something DDD
sem_wait(&data_ready); //slave may sleep here, and maybe interrupted by signal, need to handle that
//slave do something EEE
sem_post(&slave_done);
//slave do something FFF

您可能会发现执行顺序应该是 AAA [BBB/DDD] EEE [FFF/CCC],它确保主>AAA 在从>EEE 之前,后者在主>CCC 之前运行。