簡體   English   中英

如何同步三個線程?

[英]How can I synchronize three threads?

我的應用程序由主進程和兩個線程組成,所有線程都同時運行並使用三個fifo-queue:

fifo-q是Qmain,Q1和Q2。 在內部,隊列每個都使用一個計數器,當項目被放入隊列時遞增,並在項目從隊列中“獲取”時遞減。

處理涉及兩個線程,
QMaster,從第一季度和第二季度開始,並投入Qmain,
監視器,進入Q2,
和主要過程,從Qmain獲得並進入Q1。

QMaster線程循環連續檢查Q1和Q2的計數,如果任何項目在q中,則得到它們並將它們放入Qmain。

Monitor-thread循環從外部源獲取數據,打包並將其放入Q2。

應用程序的主進程還運行一個循環來檢查Qmain的計數,如果有任何項,則在循環的每次迭代中從Qmain獲取一個項並進一步處理它。 在此處理期間,它偶爾會將一個項目放入Q1中以便稍后處理(當它從Qmain依次獲取時)。

問題:
我已經實現了如上所述的所有內容,它可以隨機(短)時間工作,然后掛起。 我已經設法確定在fifo-q計數的遞增/遞減中發生的崩潰的來源(它可能發生在任何一個中)。

我嘗試過的:
使用三個互斥鎖:QMAIN_LOCK,Q1_LOCK和Q2_LOCK,只要在相關的fifo-q上完成任何get / put操作,我就會鎖定它。 結果:應用程序無法啟動,只是掛起。

主進程必須一直繼續運行,不能在'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() {...}
};

在鎖定第二個互斥鎖時,不應該鎖定第二個互斥鎖。

由於問題用C ++標記,我建議在隊列類的get / add邏輯中實現鎖定(例如使用boost鎖)或者如果你的隊列不是類,則寫一個包裝器。

這允許您簡化鎖定邏輯。

關於你添加的源:隊列大小檢查和后續put / get應該在一個事務中完成,否則另一個線程可以編輯隊列

使用調試器。 當您使用互斥鎖的解決方案掛起時,請查看線程正在執行的操作,您將對問題的原因有所了解。

你的平台是什么? 在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()> 0”之后你可以做一個“get()”並得到一個元素?

我在下面寫了一個簡單的應用:

#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;
}

我將如何調整設計並鎖定隊列訪問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() {...}
};

另請注意,互斥鎖仍然需要在此處的示例中進行初始化,並且count()也應該使用互斥鎖

您是否同時獲得多個鎖? 這通常是您想要避免的。 如果必須,請確保始終在每個線程中以相同的順序獲取鎖(這對您的並發性更具限制性,以及您通常希望避免它的原因)。

其他並發建議:您是否在讀取隊列大小之前獲取鎖定? 如果您使用互斥鎖來保護隊列,那么您的隊列實現不是並發的,您可能需要在讀取隊列大小之前獲取鎖。

由於此規則可能會出現1個問題“主進程必須一直繼續運行,不得在'讀'上阻止”。 你是如何實現它的? 'get'和'read'之間有什么區別?

問題似乎在你的實現中,而不是在邏輯中。 正如你所說,你不應該處於任何死鎖狀態,因為你沒有獲得另一把鎖,無論是否鎖定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM