我的应用程序由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() {...}
};
答案 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'之间有什么区别?
问题似乎出现在您的实现中,而不是逻辑中。正如你所说,你不应该处于任何死锁状态,因为你没有获得另一把锁,无论是否锁定。