如何强制线程严格按顺序工作?

时间:2013-10-31 20:19:01

标签: c++ multithreading winapi synchronization

我正在编写一个多线程控制台应用程序,使用WinAPI的关键部分作为同步机制。我需要创建5个线程,每个线程都有自己的字符串由线程显示。线程应该逐个顺序输出它们的字符串。

所以,我有线程功能:

void thread_routine(void* data)
{
    std::string* st = (std::string*)data;
    while(1)
    {
        /* enter critical section */
        std::cout << st; // not exactly that way, but I hope that you understood what I've meant.
        Sleep(100);
        /* leave critical section */
    }
}

在我的main()

for(int i = 0; i < 10; ++i)
    _beginthread(thread_routine, 0, strings[i]);

我希望看到类似 [o1]

的内容
1) First thread
2) Second thread
3) Third thread
4) Fourth thread
5) Fifth thread
... and so on

但是我没有看到输出,而是看到了 [o2]

1) First thread
1) First thread
3) Third thread
2) Second thread
5) Fifth thread
... and so on

我清楚地了解会发生什么:线程通过关键部分的顺序是未知的,因此该部分将被随机捕获,但我需要同步我的线程以获得类似的输出[ O1] 。那我该怎么办?有没有图案?

6 个答案:

答案 0 :(得分:3)

关键部分不是解决此问题的方法。关键部分只不过是一个互斥设备。它旨在确保在给定时间只有一个线程可以执行特定的代码。它不是用于测序,也不是特别适合该任务。问题是你不能在没有获取它的情况下等待关键部分。

考虑一种情况,其中线程B必须等待线程A完成才能继续其工作。如果你使用一个临界区(称之为cs1),那么你必须确保线程A在线程B尝试获取它之前获取它。这意味着您必须100%确定线程A开始执行并获取关键部分,然后线程B才能潜在获取关键部分。

另一方面,

Event Objects允许您等待事件或在事件已经发生时继续。因此,线程B可以在线程A开始之前等待事件。它会等到线程A(或某人)设置事件。您描述的问题(线程B需要等待某个事件发生)完全事件对象旨在解决的事物的类型。

鉴于有3个线程并行处理一段时间,但在某些时候需要等待其他事件发生,你需要编写代码来创建并等待事件。简言之:

HANDLE event1_done = CreateEvent(...);
HANDLE event2_done = CreateEvent(...);
HANDLE event3_done = CreateEvent(...);

所有事件都是手动重置,并且未发出初始状态信号。

主线程启动三个事件。然后等待第三个完成:

WaitForSingleObject(event3_done, INFINITE);

各个线程进行处理并等待各自的事件。当然,线程1不会等待。它就是这样做的:

// thread 1
// do processing
// then signal that it's finished
SetEvent(event1_done);

线程2进行处理,等待event1_done,然后设置event2_done

// thread 2
// do processing
// wait for the first thread to complete
WaitForSingleObject(event1_done, INFINITE);
// do whatever
// and then signal that it's done
SetEvent(event2_done);

线程3就像线程2一样;只有事件名称已更改。

此方法与使用关键部分之间的区别在于多个线程可能正在等待同一事件对象。设置该事件后,将释放等待该对象的所有线程。如果您只想释放其中一个,那么您将使用自动重置事件。

另请注意,如果要重新使用这些事件,则需要重置它们(调用ResetEvent)。当你这样做取决于你。

答案 1 :(得分:2)

您需要编写自己的调度程序。只是另一个以指定顺序唤醒您的线程的线程。在这种情况下,你必须传递更复杂的数据,包括一些可等待的对象(即信号量)。我在WinAPI方面没有经验,这只是一个想法:

void scheduler_thread(void* data) {
  scheduler_data* threads = (scheduler_data*)data;
  int index = 0;
  while (true) {
    notify_waitable_object(threads->waitable_object[index]);
    sleep(timeout);
    index = (index + 1) % threads->thread_count;
  }
}

void thread_procedure(void* data) {
  some_thread_data* p = (some_thread_data*)data;
  while(true)
  {
    wait_for_notify(p->wait_object);
    std::cout << p->st;
  }  
}

答案 2 :(得分:1)

1)您不应将I / O放在单独的线程中。在线程完成处理后,在main线程中输出。

2)如果需要线程按顺序执行,则不需要线程。线程对并行操作很有用。如果你需要按顺序执行某些操作,只需在一个线程中执行(main线程)。

3)如果你真的想要这样做,你会想要创建一系列关键部分,其中每个线程在另一个线程上等待。也就是说,

  • 线程2正在等待线程1清除严重第1节
  • 线程3正在等待线程2清除严重第2节
  • 线程4正在等待线程3清除严重第3节 等

此外,您的data对象(传递到每个线程)不仅需要包含您要打印的std::string,还需要包含它必须等待的关键部分的句柄for和clear(也就是说,你需要一个至少有2个关键部分句柄和一个字符串的结构)。一个不完整的例子来证明这个想法如下:

struct ThreadData
{
    std::string st;
    CRITICAL_SECTION* prevCS;
    CRITICAL_SECTION* nextCS;
};

class Mutex
{
public:
    Mutex(CRITICAL_SECTION* cs) : _cs(cs)
    {
        EnterCriticalSection(_cs);
    }

    ~Mutex()
    {
        LeaveCriticalSection(_cs);
    }
private:
    CRITICAL_SECTION* _cs;
};

void thread_routine(void* data)
{
    ThreadData* d = (ThreadData*)data;
    std::unique_ptr<Mutex> current; // this is the one my predecessor must clear
    std::unique_ptr<Mutex> next; // this is the one I have to clear
    if (d->nextCS)
    {
        next = std::make_unique<Mutex>(d->nextCS);
    }

    if (d->prevCS)
    {
        current = std::make_unique<Mutex>(d->prevCS);
    }

    std::cout << d->st << std::endl;
}

int main()
{
    CRITICAL_SECTION cs1;
    CRITICAL_SECTION cs2;

    InitializeCriticalSection(&cs1);  
    InitializeCriticalSection(&cs2);

    ThreadData td1;
    ThreadData td2;
    ThreadData td3;

    td1.nextCS = &cs1;
    td1.prevCS = nullptr;
    td1.st = "Thread 1";

    td2.nextCS = &cs2;
    td2.prevCS = &cs1;
    td2.st = "Thread 2";

    td3.nextCS = nullptr;
    td3.prevCS = &cs2;
    td3.st = "Thread 3";     

    _beginthread(thread_routine, 0, &td1);
    _beginthread(thread_routine, 0, &td2);
    _beginthread(thread_routine, 0, &td3);

   // NOTE:  you also need to add an event handle to wait for in the main thread.  It would go here   

    return 0;
}

替代解决方案

(可能满足或不满足您的任务需求):

您一次只能运行1个线程,让主线程等待单个线程完成,然后让下一个线程启动。也就是说,您创建所有线程然后处于挂起状态。然后,主要开始你的第一个线程并等待它完成,然后开始第二个线程,并重复你拥有的线程数。根据要求的编写方式,这可能是一种聪明的方式来消除教授任务的愚蠢。

答案 3 :(得分:1)

您可能遇到设计问题并应修复该问题!

但如果你真的要同步线程,这是一种方法。可能会为此投票,因为这是非常低效的,并且如果任何线程跳过重要部分(例如通过try-catch)将会死锁,但仍然是一种方法:

#include <thread>
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <vector>

void myFunc(int idx,std::atomic<int> * counter){

    std::this_thread::sleep_for(std::chrono::milliseconds(std::rand()%100));
    // Don't know about memory_order stuff
    // If you want to use this approach, you should read about it.
    while(counter->load()!=idx){
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    std::cout << idx << " thread here\n";
    counter->store(idx+1);
}

int main(){
    std::srand(std::time(0));
    std::atomic<int> counter(0);
    std::vector<std::thread> thread_vector;
    for(int i=0;i<10;i++)
        thread_vector.push_back(std::thread(myFunc,i,&counter));

    for(int i=0;i<10;i++)
        thread_vector[i].join();
}

答案 4 :(得分:0)

我创建了一个简单的生产者,消费者场景,我有一个函数&#34; producer&#34;产生数据和N(N是可配置的)消费者,按顺序(严格的顺序)消耗数据,即第一个线程应该消耗第二个线程之前的数据。和第3个之前的第3个线程,依此类推。当每个消费者消费数据时,它将再次从第一个线程开始。

要按顺序运行它我正在使用条件变量和&#34; notify_one()&#34;功能。

#include <iostream>
#include <queue>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>

#define NUMBER_OF_THREADS 5
#define MAX_QUEUE_ELEM 500

std::mutex m;
std::condition_variable producerMutex;
std::condition_variable consumerMutex[NUMBER_OF_THREADS];
std::queue<int> Q;
static int elemCounter = 1;
static int threadCounter = 1;

void producer()
{
    while (true)
    {
        // lock thread
        std::unique_lock<std::mutex> lock(m);

        while (Q.size() == MAX_QUEUE_ELEM)
        {
            std::cout << "Producer waiting" << std::endl;
            producerMutex.wait(lock);
        }

        Q.push(elemCounter++);

        // unlock thread
        lock.unlock();

        // notify next waiting consumer thread
        consumerMutex[threadCounter].notify_one();
    }
}

void consumer()
{
    while (true)
    {
        //lock thread
        std::unique_lock<std::mutex> lock(m);

        while (Q.empty())
        {
            std::cout << "Consumer waiting : %d "<< threadCounter << std::endl;
            consumerMutex[threadCounter].wait(lock);
        }

        int val = Q.front();
        Q.pop();

        // Printing in lock to print in sequnce
        std::cout << "val: %d " << val << " , thread number: %d " << threadCounter << std::endl;

        // unloack thread
        lock.unlock();

        // Notify to next waiting thread in sequnce
        if (threadCounter == NUMBER_OF_THREADS)
        {
            // it means this is last thread so notify first thread
            threadCounter = 1;
            consumerMutex[threadCounter].notify_one();
        }
        else
        {
            consumerMutex[++threadCounter].notify_one();
        }
    }
}

int main()
{
    std::thread thrds[NUMBER_OF_THREADS];

    for (int i = 0; i < NUMBER_OF_THREADS; i++)
    {
        thrds[i] = std::thread(consumer);
    }

    producer();

    for (int i = 0; i < NUMBER_OF_THREADS; i++)
    {
        thrds[i].join();
    }

    return 0;
}

答案 5 :(得分:-1)

/** For Scheduling the thread execution order, Evens are enough, no synchronization objects like Mutex, Critical Section etc.. are not needed. Below is a code and which is scalable based on the number of threads entered. */

//Code for Schedule threads execution in a order T1->T2->T3-> ..->Tn->T1..
// where T1 is Thread-1, T2 is Thread-2 .. Tn is Thread-N

#include <iostream>
#include <Windows.h>

HANDLE  * EventsHandlesArray;    
HANDLE * ThreadHandlesArray;

int iMaxWaitTime = 10000; // 10 Secs    
int iThreadWaitTimeInterval = 1000; //1 sec

int iNumberOfThread = 3; // You can change this no. of thread value within the permitted limit by your system :)

DWORD WINAPI ThreadFun( LPVOID lpParam )

{
    int iThreadIndex = reinterpret_cast<int> (lpParam);
    int iCurrentEVentIndex = iThreadIndex == 1 ? iNumberOfThread-1: iThreadIndex-2;
        int iNewEVentIndex = iThreadIndex -1;

    while(1)
    {
        switch(::WaitForSingleObject(EventsHandlesArray[iCurrentEVentIndex],iMaxWaitTime))
        {
            case WAIT_OBJECT_0:
                    std::cout<<" I am a thread "<<iThreadIndex<<std::endl;
                    ResetEvent ( EventsHandlesArray[iCurrentEVentIndex]);
                    Sleep (iThreadWaitTimeInterval);
                    SetEvent ( EventsHandlesArray[iNewEVentIndex]);
                break;

            case WAIT_FAILED:
                    std::cout<<"Error in thread"<<iThreadIndex<<" Thread wait time failed "<<std::endl;
                   ResetEvent ( EventsHandlesArray[iCurrentEVentIndex]);
                   SetEvent ( EventsHandlesArray[iCurrentEVentIndex]);
                break;

            case WAIT_TIMEOUT:
                    std::cout<<"Error in thread"<<iThreadIndex<<" Thread wait timed out"<<std::endl;
                    ResetEvent ( EventsHandlesArray[iCurrentEVentIndex]);
                   SetEvent ( EventsHandlesArray[iCurrentEVentIndex]);
                break;
        }
    }
}

int main(void)

{       
__try
    {
       std::cout<<"Running Main, Creating Events"<<std::endl;
       EventsHandlesArray = new HANDLE[iNumberOfThread];
       ThreadHandlesArray = new HANDLE[iNumberOfThread];

        for(int iCount = 0; iCount<iNumberOfThread; iCount++)
        {
            EventsHandlesArray[iCount] = CreateEvent ( NULL , true , false , NULL );
        }

        for(int iCount = 0; iCount<iNumberOfThread; iCount++)
        {
            if( EventsHandlesArray[iCount] == INVALID_HANDLE_VALUE)
            {
                std::cout<<"Problem with Event Creation"<<std::endl;
                for(int iCount =0;iCount<iNumberOfThread;iCount++)
                {
                    CloseHandle(EventsHandlesArray[iCount]); 
                }
                return 0;
            }
        }

        DWORD Id=0;
        int iThreadIndex = 1;

        for(int iCount = 0; iCount<iNumberOfThread; iCount++)
        {
            iThreadIndex = iCount+1;
            ThreadHandlesArray[iCount] = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun,(LPVOID)iThreadIndex ,CREATE_SUSPENDED,&Id );
            std::cout<<"Thread Created : "<<Id<<std::endl;
        }

        bool bThreadCreatedSuccessfully = true;

        for(int iCount = 0; iCount<iNumberOfThread; iCount++)
        {
            if( ThreadHandlesArray[iCount] == INVALID_HANDLE_VALUE || ThreadHandlesArray[iCount] == NULL)
            {
                bThreadCreatedSuccessfully = false;
                break;
            }
        }

        if(bThreadCreatedSuccessfully)
        {
            std::cout<<"Resuming Threads "<<std::endl;
            for(int iCount =0;iCount<iNumberOfThread;iCount++)
            {
                //Try to close the event handles
                ResumeThread(ThreadHandlesArray[iCount]);
            }

            Sleep (iThreadWaitTimeInterval);
            SetEvent ( EventsHandlesArray[iNumberOfThread-1]);
            WaitForMultipleObjects(iNumberOfThread,ThreadHandlesArray, TRUE, INFINITE);
        }
        else
        {
            std::cout<<"Issue with Thread Creation"<<std::endl;
        }
    }

    __finally 
    {
        //Close Threads & Events
        for(int iCount=0;iCount<iNumberOfThread;iCount++)
        {
            //Try to close the event handles
            CloseHandle(ThreadHandlesArray[iCount]);
            CloseHandle(EventsHandlesArray[iCount]);
        }
        std::cout<<"  .... Exiting the program"<<std::endl;
    }
    return 0;
}