我有以下源代码(改编自我的原始代码):
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include "pthread.h"
#define MAX_ENTRY_COUNT 4
int entries = 0;
bool start = false;
bool send_active = false;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condNotEmpty = PTHREAD_COND_INITIALIZER;
pthread_cond_t condNotFull = PTHREAD_COND_INITIALIZER;
void send()
{
for (;;) {
if (!start)
continue;
start = false;
for(int i = 0; i < 11; ++i) {
send_active = true;
pthread_mutex_lock(&mutex);
while(entries == MAX_ENTRY_COUNT)
pthread_cond_wait(&condNotFull, &mutex);
entries++;
pthread_cond_broadcast(&condNotEmpty);
pthread_mutex_unlock(&mutex);
send_active = false;
}
}
}
void receive(){
for(int i = 0; i < 11; ++i){
pthread_mutex_lock(&mutex);
while(entries == 0)
pthread_cond_wait(&condNotEmpty, &mutex);
entries--;
pthread_cond_broadcast(&condNotFull);
pthread_mutex_unlock(&mutex);
}
if (send_active)
printf("x");
}
int _tmain(int argc, _TCHAR* argv[])
{
pthread_t s;
pthread_create(&s, NULL, (void *(*)(void*))send, NULL);
for (;;) {
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condNotEmpty, NULL);
pthread_cond_init(&condNotFull, NULL);
start = true;
receive();
pthread_mutex_destroy(&mutex);
mutex = NULL;
pthread_cond_destroy(&condNotEmpty);
pthread_cond_destroy(&condNotFull);
condNotEmpty = NULL;
condNotFull = NULL;
printf(".");
}
return 0;
}问题如下:在接收方法继续之前,发送函数中的最后一次解锁并未完成。在我的原始代码中,互斥锁位于一个对象中,这些对象在完成工作后被删除。如果send方法还没有完成最后一次解锁,那么互斥锁是无效的,并且我的程序会导致解锁失败。
通过运行程序可以很容易地重现这种行为:每次显示"x“时,接收方法已接近完成,而发送方法在解锁调用中”挂起“。
我已经用VS2008和VS2010编译过了--两个结果是一样的。
pthread_mutex_unlock不是原子的,这就解决了问题。我该如何解决这个问题?欢迎任何评论……
诚挚的问候
迈克尔
发布于 2010-12-08 22:09:54
您的printf("x")是一个典型的竞态条件示例。
在pthread_mutex_unlock()之后,OS可以自由地在任何时间内不调度这个线程:滴答、秒或天。你不能假设send_active最终会被“证伪”。
发布于 2010-12-08 23:01:02
根据定义,pthread_mutex_unlock()必须在互斥锁返回之前释放它。一旦互斥锁被释放,另一个争用互斥锁的线程就会被调度。请注意,即使pthread_mutex_unlock()可以安排在互斥锁返回之前不释放它(我认为您说的是原子的),仍然会有一个与您现在看到的东西等价的竞争条件(我不清楚您看到的是什么竞争,因为有评论表明您对访问send_active来控制printf()调用的竞争条件并不真正感兴趣)。
在这种情况下,另一个线程可以被调度到pthread_mutex_unlock()的“行间”,以及调用它的函数中的以下语句/表达式-你会有相同的竞争条件。
发布于 2010-12-09 14:31:33
这里有一些关于可能发生的事情的猜测。对这一分析有几个警告:
当调用pthread_mutex_unlock()时,它会递减锁计数,如果锁计数降为零,则会在关联的事件对象上调用Win32 SetEvent()应用编程接口,以便解除对等待互斥锁的任何线程的阻塞。非常标准的东西。
这就是投机的原因。假设已经调用了SetEvent()来解除阻塞等待互斥锁的线程,并且它向与它被提供的句柄相关联的事件发出信号(这是它应该做的)。但是,在SetEvent()函数执行其他操作之前,另一个线程开始运行并关闭调用该特定SetEvent()的事件对象句柄(通过调用pthread_mutex_destroy())。
现在,正在进行的SetEvent()调用有一个不再有效的句柄值。我想不出在通知事件之后SetEvent()会对该句柄做什么特殊原因,但它可能会这样做(我还可以想象有人会提出一个合理的论点,即SetEvent()应该能够期望事件对象句柄在调用期间保持有效)。
如果这就是发生在你身上的事情(这是一个很大的假设),我不确定是否有简单的解决办法。我认为pthread库必须进行更改,以便在调用SetEvent()之前复制事件句柄,然后在SetEvent()调用返回时关闭该副本。这样,即使“main”句柄被另一个线程关闭,句柄也将保持有效。我猜它必须在很多地方做到这一点。这可以通过将受影响的Win32应用程序接口调用替换为对执行“复制处理/调用应用编程接口/关闭复制”序列的包装函数的调用来实现。
对于您来说,尝试对pthread_mutex_unlock()中的SetEvent()调用进行此更改并不是不合理的,并查看它是否解决(或至少改进)您的特定问题。如果是这样的话,你可能想要联系这个库的维护者,看看是否可以以某种方式安排一个更全面的修复(做好准备--你可能会被要求做大部分工作)。
出于好奇,在调试pthread_mutex_unlock()/SetEvent()中挂起的线程的状态时,您有关于到底发生了什么的信息吗?SetEvent()在等什么?(与Visual Studio调试器相比,Windows调试工具包中的cdb调试器可能能够提供更多有关这方面的信息)。
此外,请注意pthread_mutex_destroy()源代码中的以下注释,它似乎与您的特定问题相关(但不同):
/*
* FIXME!!!
* The mutex isn't held by another thread but we could still
* be too late invalidating the mutex below since another thread
* may already have entered mutex_lock and the check for a valid
* *mutex != NULL.
*
* Note that this would be an unusual situation because it is not
* common that mutexes are destroyed while they are still in
* use by other threads.
*/https://stackoverflow.com/questions/4387327
复制相似问题