pthread_mutex_unlock不是原子的

时间:2010-12-08 12:18:31

标签: c multithreading winapi pthreads

我有以下源代码(改编自原始代码):

#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函数中的最后一次解锁有时不会完成。在我的原始代码中,互斥锁位于执行作业后删除的对象中。如果send方法还没有完成上次解锁,则互斥锁无效,我的程序导致解锁失败。

通过运行程序可以轻松再现行为:每次显示“x”时,接收方法已接近完成,并且发送方法在解锁通话中“挂起”。

我用VS2008和VS2010编译 - 两个结果都是一样的。

pthread_mutex_unlock不是原子的,这可以解决问题。我该如何解决这个问题?欢迎提出任何意见......

祝你好运

迈克尔

4 个答案:

答案 0 :(得分:1)

您的printf(“x”)是教科书竞赛条件示例。

在pthread_mutex_unlock()之后,OS可以自由地安排此线程任何时间:ticks,seconds或days。你不能假设send_active会及时“伪造”。

答案 1 :(得分:1)

根据定义,

pthread_mutex_unlock()必须在返回之前释放互斥锁。释放互斥锁的瞬间,可以安排另一个争用互斥锁的线程。请注意,即使pthread_mutex_unlock()可以安排在它返回之后才释放互斥锁(我认为你的意思是它是原子的),所以你现在看到的任何东西都会有相同的竞争条件(它是我不清楚你看到了什么种族,因为评论表明你并不真正对访问send_active来控制printf()电话的竞争条件感兴趣。

在这种情况下,另一个线程可以被调度为pthread_mutex_unlock()之间的“行间”和调用它的函数中的以下语句/表达式 - 你将拥有相同的竞争条件。

答案 2 :(得分:0)

以下是对可能发生的事情的一些猜测。关于这一分析的几点警告:

  • 这是基于您使用http://sourceware.org/pthreads-win32/
  • 中的Win32 pthreads包的假设
  • 这只是基于对该网站上的pthreads源的非常简要的检查以及问题和评论中的信息 - 我没有机会实际尝试运行/调试任何代码。

当调用pthread_mutex_unlock()时,它会减少锁定计数,如果该锁定计数降至零,则会在关联的事件对象上调用Win32 SetEvent() API,以允许任何线程在互斥锁上等待畅通无阻。非常标准的东西。

这是猜测的来源。让我们说SetEvent()已经被调用来解锁在互斥锁上等待的线程,它会发出与给定的句柄相关的事件(应该这样)。但是,在SetEvent()函数执行任何其他操作之前,另一个线程开始运行并关闭调用该特定SetEvent()的事件对象句柄(通过调用pthread_mutex_destroy())。

现在正在进行的SetEvent()调用具有不再有效的句柄值。我想不出SetEvent()在发出事件信号之后会对该句柄做任何事情的特殊原因,但可能确实如此(我也可以想象有人提出SetEvent()应该能够做出的合理论证期望事件对象句柄在API调用期间保持有效。

如果这是你发生的事情(如果是),我不确定是否有一个简单的解决方法。我认为pthreads库必须进行更改,以便在调用SetEvent()之前复制事件句柄,然后在SetEvent()调用返回时关闭该副本。这样,即使'main'句柄被另一个线程关闭,句柄仍然有效。我猜它必须在很多地方做到这一点。这可以通过使用对执行“重复句柄/调用API /关闭重复”序列的包装函数的调用替换受影响的Win32 API调用来实现。

尝试对SetEvent()中的pthread_mutex_unlock()电话进行此更改并查看它是否解决(或至少改善)您的特定问题可能并非不合理。如果是这样,您可能希望联系图书馆的维护者,看看是否可能以某种方式安排更全面的修复(做好准备 - 可能会要求您做大部分工作)。

出于好奇,在调试pthread_mutex_unlock() / SetEvent()中挂起的线程的状态时,您是否有关于发生什么事情的信息? SetEvent()正在等待什么? (调试工具for Windows软件包中的cdb调试器可能比Visual Studio调试器更能提供有关此内容的信息)。

另外,请注意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.
*/

答案 3 :(得分:0)

迈克尔,谢谢你的调查和评论!

我使用的代码来自http://sourceware.org/pthreads-win32/

您在第三和第四段中描述的情况正是发生的事情。

我已经检查了一些解决方案,一个简单的解决方案似乎对我有用:我等待发送功能(和SetEvent)完成。到目前为止,我对此解决方案的所有测试都是成功我将在周末进行更大规模的测试。