如何同步三个线程?

时间:2010-10-14 07:34:45

标签: c++ multithreading mutex synchronize

我的应用程序由main-process和两个线程组成,所有线程都同时运行并使用三个fifo-queue:

fifo-q是Qmain,Q1和Q2。在内部,每个队列都使用一个计数器,当一个项目被放入队列时,该计数器会递增,当一个项目从队列中“获取”时递减。

处理涉及两个线程,
QMaster,从第一季度和第二季度开始,并投入Qmain,
监视器,进入Q2,
和主要过程,从Qmain进入并进入第一季。

QMaster-thread循环连续检查Q1和Q2的计数,如果有任何项目在q中,则得到它们并将它们放入Qmain。

Monitor-thread循环从外部源获取数据,打包并将其放入Q2。

应用程序的主进程还运行一个循环检查Qmain的计数,如果有任何项目,得到的是一个项目 来自Qmain的循环的每次迭代并进一步处理它。在此处理过程中偶尔会有 将一个项目放入Q1以便稍后处理(当它从Qmain轮流获得时)。

问题:
我已经实现了如上所述的所有内容,它可以随机(短)时间工作,然后挂起。 我已经设法确定崩溃的来源发生在增量/减量 计算一个fifo-q(它可能发生在任何一个中)。

我尝试了什么:
使用三个互斥锁:QMAIN_LOCK,Q1_LOCK和Q2_LOCK,无论何时进行任何get / put操作,我都会锁定 是在相关的fifo-q上完成的。结果:应用程序无法启动,只是挂起。

主进程必须一直继续运行,不能在'read'上阻塞(命名管道失败,socketpair失败)。

有什么建议吗? 我想我没有正确实现互斥锁,应该怎么做? (关于改进上述设计的任何意见也欢迎)

下面的

[edit]是进程和fifo-q-template:
哪里&我应该如何放置互斥锁以避免上述问题?

main-process:
...
start thread QMaster
start thread Monitor
...
while (!quit)
{
    ...
    if (Qmain.count() > 0)
    {
        X = Qmain.get();
        process(X) 
            delete X;
    }
    ...
    //at some random time:
    Q2.put(Y);
    ...
}

Monitor:
{
    while (1)
    {
        //obtain & package data
        Q2.put(data)
    }
}

QMaster:
{
    while(1)
    {
        if (Q1.count() > 0)
            Qmain.put(Q1.get());

        if (Q2.count() > 0)
            Qmain.put(Q2.get());
    }
}

fifo_q:
template < class X* > class fifo_q
{
    struct item
    {
        X* data;
        item *next;
        item() { data=NULL; next=NULL; }
    }
    item *head, *tail;
    int count;
public:
    fifo_q() { head=tail=NULL; count=0; }
    ~fifo_q() { clear(); /*deletes all items*/ }
    void put(X x) { item i=new item(); (... adds to tail...); count++; }
    X* get() { X *d = h.data; (...deletes head ...); count--; return d; }
    clear() {...}
};

6 个答案:

答案 0 :(得分:1)

当您锁定第二个互斥锁时,不应该锁定第二个互斥锁。

由于问题是用C ++标记的,我建议在队列类的get / add逻辑中实现锁定(例如使用boost锁)或者如果你的队列不是类,则写一个包装器。

这允许您简化锁定逻辑。

关于你添加的来源:队列大小检查和后面的put / get应该在一个事务中完成,否则另一个线程可以在其间编辑队列

答案 1 :(得分:1)

使用调试器。当您使用互斥锁的解决方案挂起时,请查看线程正在执行的操作,您将对问题的原因有所了解。

你的平台是什么?在Unix / Linux中,您可以使用POSIX消息队列(您也可以使用System V消息队列,套接字,FIFO,...),因此您不需要互斥锁。

了解条件变量。按照你的描述看起来你的Qmaster线程正在忙着循环,烧掉你的CPU。

您的一条回复建议您执行以下操作:

Q2_mutex.lock()
Qmain_mutex.lock()
Qmain.put(Q2.get())
Qmain_mutex.unlock()
Q2_mutex.unlock()

但你可能想这样做:

Q2_mutex.lock()
X = Q2.get()
Q2_mutex.unlock()

Qmain_mutex.lock()
Qmain.put(X)
Qmain_mutex.unlock()

正如上面Gregory所建议的,将逻辑封装到get / put中。

编辑:既然您发布了我的代码,我想知道,这是一个学习练习吗? 因为我看到你正在编写自己的FIFO队列类而不是使用C ++标准的std :: queue。我想你已经很好地测试了你的课程,问题就不存在了。

另外,我不明白你为什么需要三个不同的队列。似乎Qmain队列就足够了,然后你就不需要真正忙着等待的Qmaster线程了。

关于封装,您可以创建一个封装fifo_q类的synch_fifo_q类。添加一个私有互斥变量,然后公共方法(put,get,clear,count,...)应该像put(X){lock m_mutex; m_fifo_q.put(X);解锁m_mutex; }

问题:如果队列中有多个读者,会发生什么?是否保证在“count()&gt; 0”之后你可以做一个“get()”并得到一个元素?

答案 2 :(得分:1)

我在下面写了一个简单的应用程序:

#include <queue>
#include <windows.h>
#include <process.h>
using namespace std;

queue<int> QMain, Q1, Q2;
CRITICAL_SECTION csMain, cs1, cs2;

unsigned  __stdcall TMaster(void*)
{
    while(1)
    {
        if( Q1.size() > 0)
        {
            ::EnterCriticalSection(&cs1);
            ::EnterCriticalSection(&csMain);
            int i1 = Q1.front();
            Q1.pop();
            //use i1;
            i1 = 2 * i1;
            //end use;
            QMain.push(i1);
            ::LeaveCriticalSection(&csMain);
            ::LeaveCriticalSection(&cs1);
        }
        if( Q2.size() > 0)
        {
            ::EnterCriticalSection(&cs2);
            ::EnterCriticalSection(&csMain);
            int i1 = Q2.front();
            Q2.pop();
            //use i1;
            i1 = 3 * i1;
            //end use;
            QMain.push(i1);
            ::LeaveCriticalSection(&csMain);
            ::LeaveCriticalSection(&cs2);
        }
    }
    return 0;
}

unsigned  __stdcall TMoniter(void*)
{
    while(1)
    {
        int irand = ::rand();
        if ( irand % 6 >= 3)
        {
            ::EnterCriticalSection(&cs2);
            Q2.push(irand % 6);
            ::LeaveCriticalSection(&cs2);
        }
    }
    return 0;
}

unsigned  __stdcall TMain(void)
{
    while(1)
    {
        if (QMain.size() > 0)
        {
            ::EnterCriticalSection(&cs1);
            ::EnterCriticalSection(&csMain);
            int i = QMain.front();
            QMain.pop();
            i = 4 * i;
            Q1.push(i);
            ::LeaveCriticalSection(&csMain);
            ::LeaveCriticalSection(&cs1);
        }
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    ::InitializeCriticalSection(&cs1);
    ::InitializeCriticalSection(&cs2);
    ::InitializeCriticalSection(&csMain);
    unsigned threadID;
    ::_beginthreadex(NULL, 0, &TMaster, NULL, 0, &threadID);
    ::_beginthreadex(NULL, 0, &TMoniter, NULL, 0, &threadID);
    TMain();

    return 0;
}

答案 3 :(得分:1)

我将如何调整设计并锁定队列访问posix方式的示例。 请注意我将包装互斥锁以使用RAII或使用boost-threading,并且我将使用stl :: deque或stl :: queue作为队列,但尽可能保持与您的代码尽可能接近:

main-process:
...
start thread Monitor
...
while (!quit)
{
    ...
    if (Qmain.count() > 0)
    {
        X = Qmain.get();
        process(X) 
            delete X;
    }
    ...
    //at some random time:
    QMain.put(Y);
    ...
}

Monitor:
{
    while (1)
    {
        //obtain & package data
        QMain.put(data)
    }
}

fifo_q:
template < class X* > class fifo_q
{
    struct item
    {
        X* data;
        item *next;
        item() { data=NULL; next=NULL; }
    }
    item *head, *tail;
    int count;
    pthread_mutex_t m;
public:
    fifo_q() { head=tail=NULL; count=0; }
    ~fifo_q() { clear(); /*deletes all items*/ }
    void put(X x) 
    { 
      pthread_mutex_lock(&m);
      item i=new item(); 
      (... adds to tail...); 
      count++; 
      pthread_mutex_unlock(&m);
    }
    X* get() 
    { 
      pthread_mutex_lock(&m);
      X *d = h.data; 
      (...deletes head ...); 
      count--; 
      pthread_mutex_unlock(&m);
      return d; 
    }
    clear() {...}
};

另请注意,互斥锁仍需要像示例here中那样初始化,而count()也应该使用互斥锁

答案 4 :(得分:0)

您是否同时获得多个锁?这通常是您想要避免的。如果必须,请确保始终在每个线程中以相同的顺序获取锁(这对您的并发性更具限制性,以及您通常希望避免它的原因)。

其他并发建议:您是否在读取队列大小之前获取锁?如果您使用互斥锁来保护队列,那么您的队列实现不是并发的,您可能需要在读取队列大小之前获取锁。

答案 5 :(得分:0)

由于此规则可能会出现1个问题“主进程必须一直继续运行,不得在'read'上阻止”。你是如何实现它的? 'get'和'read'之间有什么区别?

问题似乎出现在您的实现中,而不是逻辑中。正如你所说,你不应该处于任何死锁状态,因为你没有获得另一把锁,无论是否锁定。