2010-09-03 153 views
5

我对顺序创建的线程的执行顺序有问题。 这里是代码。boost ::线程执行顺序

#include <iostream> 
#include <Windows.h> 
#include <boost/thread.hpp> 

using namespace std; 

boost::mutex mutexA; 
boost::mutex mutexB; 
boost::mutex mutexC; 
boost::mutex mutexD; 


void SomeWork(char letter, int index) 
{ 
    boost::mutex::scoped_lock lock; 
    switch(letter) 
    { 
    case 'A' : lock = boost::mutex::scoped_lock(mutexA); break; 
    case 'B' : lock = boost::mutex::scoped_lock(mutexB); break; 
    case 'C' : lock = boost::mutex::scoped_lock(mutexC); break; 
    case 'D' : lock = boost::mutex::scoped_lock(mutexD); break; 
    } 

    cout << letter <<index << " started" << endl; 
    Sleep(800); 
    cout << letter<<index << " finished" << endl; 
} 

int main(int argc , char * argv[]) 
{ 
    for(int i = 0; i < 16; i++) 
    { 
     char x = rand() % 4 + 65; 
     boost::thread tha = boost::thread(SomeWork,x,i); 
     Sleep(10); 
    } 
Sleep(6000); 
    system("PAUSE"); 
    return 0; 
} 

每次将一个字母(从A到D)和一个genereaion id(i)传递给SomeWork作为线程的方法。我不关心字母之间的执行顺序,但是对于特定的字母,如A,Ax必须在Ay之前开始,如果x < y。 代码的随机输出的一个随机部分是:

 
B0 started 
D1 started 
C2 started 
A3 started 
B0 finished 
B12 started 
D1 finished 
D15 started 
C2 finished 
C6 started 
A3 finished 
A9 started 
B12 finished 
B11 started --> B11 started after B12 finished. 
D15 finished 
D13 started 
C6 finished 
C7 started 
A9 finished 

如何避免这种情况?
谢谢。


我使用条件变量解决了这个问题。但我改变了一些问题。解决方案是跟踪for循环的索引。所以每个线程知道什么时候不工作。但就这个代码而言,我还想问一下另外两件事情。
首先,在我的电脑上,当我将for-loop索引设置为350时,我有访问冲突。 310是循环的数量,这是可以的。所以我意识到有最大数量的线程要生成。我如何确定这个数字?其次,在Visual Studio 2008中,代码的发布版本显示出一种非常奇怪的行为。如果不使用条件变量(第1行至第3行被注释掉),那么线程就会被排序。这怎么可能发生?

这里是代码:

#include <iostream> 
#include <Windows.h> 
#include <boost/thread.hpp> 

using namespace std; 

boost::mutex mutexA; 
boost::mutex mutexB; 
boost::mutex mutexC; 
boost::mutex mutexD; 


class cl 
{ 
public: 
    boost::condition_variable con; 
    boost::mutex mutex_cl; 
    char Letter; 
    int num; 
    cl(char letter) : Letter(letter) , num(0) 
    { 

    } 
    void doWork(int index, int tracknum) 
    { 
     boost::unique_lock<boost::mutex> lock(mutex_cl); 
     while(num != tracknum)  // line 1 
      con.wait(lock); // line 2 
     Sleep(10); 
     num = index; 
     cout << Letter<<index << endl; 
     con.notify_all(); // line 3 
    } 
}; 

int main(int argc , char * argv[]) 
{ 
    cl A('A'); 
    cl B('B'); 
    cl C('C'); 
    cl D('D'); 

    for(int i = 0; i < 100; i++) 
    { 
     boost::thread(&cl::doWork,&A,i+1,i); 
     boost::thread(&cl::doWork,&B,i+1,i); 
     boost::thread(&cl::doWork,&C,i+1,i); 
     boost::thread(&cl::doWork,&D,i+1,i); 
    } 
    cout << "************************************************************************" << endl; 

    Sleep(6000); 
    system("PAUSE"); 
    return 0; 
} 

回答

6

如果你有两个不同的线程等待锁,它完全是非确定性的哪一个会获得一次锁被先前持有人释放。我相信这是你正在经历的。假设B10持有该锁,同时产生线程B11B12B10释放锁 - 无论首先创建哪个线程,或者哪个线程先开始等待,无论是B11还是B12接下来获取它,都是硬币折腾。

也许你应该为每个字母实现工作队列,这样你就产生了正好4个线程,每个线程都消耗工作单元?这是以这种方式轻松保证订购的唯一方法。如果多个线程正在等待锁定,一个简单的互斥锁不会保证排序。

+0

你的工作单位是什么意思? – 2010-09-03 13:30:10

+0

线程完成一部分工作的一些表示。在这种情况下,一个工作单位基本上是一个“(字母,索引)”对。 – Gian 2010-09-03 13:48:34

2

尽管B11在B12之前启动,但不能保证给定一个CPU时间片来执行B12之前的SomeWork()。这个决定取决于操作系统及其调度程序。

互斥锁通常用于同步线程之间的数据访问,并且已经通过线程执行序列(即数据访问)提出了一个问题。

如果组“A”的线程在相同的数据上执行相同的代码,那么就使用一个线程。这将消除组中线程之间的上下文切换并产生相同的结果。如果数据正在改变,则考虑生产者/消费者模式。 Paul Bridger给出了一个简单易懂的生产者/消费者例子here

1

你的线程有开始执行前必须满足的依赖关系。在你的例子中,B12依赖于B0和B11。不知何故,你必须跟踪依赖性知识。未完成依赖的线程必须等待。我想看看condition variables。每次线程完成SomeWork()时,它都会使用条件变量的notify_all()方法。然后所有等待的线程必须检查它们是否仍然有依赖关系。如果是这样,请回去等待。否则,请继续并调用SomeWork()。

您需要一些方法来确定每个线程是否具有未完成的依赖关系。这可能是一些全球可用的实体。你只应该修改它,当你有互斥(在SomeWork())。对于简单的数据结构,多线程读取应该是安全的。