2013-10-31 33 views
1

我正在寫一個多線程控制檯應用程序使用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]); 

嗯,我希望看到類似[01]

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

但不是這個輸出,我看到了像[O]

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

我清楚地明白髮生了什麼:這些線程通過關鍵部分的順序是未知的,所以部分將通過一個隨機捕捉,但我需要我的同步線程來獲得輸出,如[o1]。那我該怎麼做?有沒有任何模式?

+2

不要將I/O置於關鍵部分。在線程完成後進行打印*。 –

+0

@KerrekSB如何在不將IO放入關鍵部分的情況下實現順序輸出? – Netherwire

+1

線程的目的不是分離出某些不依賴於特定執行順序的計算步驟。如果你想擁有s.th.在有序執行中使用同步對象,如互斥鎖和信號量! –

回答

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)。當你這樣做取決於你。

+0

對於這種方法,你可能想使用[WaitForMultipleObjects](http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025 %28v = vs.85%29.aspx)在你的主線程中。 –

+0

@ZacHowland:你可以,但沒有必要,因爲你知道如果設置了event3_done,則另外兩個設置。 –

1

1)你不應該把I/O放在不同的線程中。在線程完成其處理後,在main線程中執行輸出。

2)如果你需要線程順序執行,你不需要線程。線程對於並行操作非常有用。如果您需要按順序執行某些操作,則只需在單個線程中執行即可(您的main線程)。 3)如果你真的想要這樣做,你會想創建一系列關鍵部分,其中每個線程都在等待不同的線程。也就是說,

  • 線程2正在等待線程1以清除臨界區1
  • 線程3在等待線程2以清除臨界區2
  • 螺紋4在等待線程3以清除臨界區3 等

此外,您data對象(被傳遞到每個線程),就需要不僅包含std::string要打印出來,還把手給它必須等待臨界區和cle ar(也就是說,你需要一個至少有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個線程一次運行,讓主線程等待一個線程完成,然後再開始下一個線程。也就是說,你創建了所有的線程並離開,然後處於暫停狀態。然後主要開始你的第一個線程,並等待它完成之後開始第二個線程,並重復你擁有的線程數。根據要求的寫法,這可能會是一種巧妙的方式來避免教授任務的愚蠢。

+0

我認爲你有一個競爭條件。如果第二個線程在第一個線程之前開始執行會發生什麼?第二個線程是否會抓住'cs1'並假定第一個線程已經釋放它? –

+0

@JimMischel這是正確的(我提到它是不完整的)。他需要開始暫停每個線程,然後依次啓動它們。 –

+0

我不確定即使這樣也行。我不認爲如果你恢復t1然後恢復t2,他們將會按照這個順序開始執行。我想你可以在恢復t1之後插入一個延遲,但插入像這樣的人爲延遲通常是不可靠的。 –

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(); 
} 
+0

好吧......但學術演習幾乎已經有設計問題,你不覺得嗎? – Netherwire

+0

@RomanChehowsky我認爲學術演習應該讓你思考這些(主要是天真的)設計問題,並且打敗你提供超越愚蠢要求(這可能看起來矛盾的一個簡單的解決方案)的'防水'解決方案。 –

+0

你應該看看'std :: future'和'std :: promise',他們可能會解決你最好的問題,如果這不是你實際解決的確切問題。 – UldisK

2

你需要編寫自己的調度。只是另一個線程以指定的順序喚醒你的線程。在這種情況下,你必須向你的線程傳遞更復雜的數據,包括一些可等待的對象(即Semaphore)。我沒有經驗的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; 
    } 
} 
+0

聽起來不錯,我會嘗試設計一些類似於你的建議。 – Netherwire

+0

已經看過SignalObjectAndWait函數,你需要這個來實現這個解決方案 – dnk

0

我創建了一個簡單的生產者,消費者場景,其中我有一個函數「生產者」生產數據和N(N是可配置)消費者,消費數據順序(嚴格的順序),即第一個線程應該消耗數據之前第二線。以及3rd之前的第二個線程等。當每個消費者使用數據時,它將再次從第一個線程開始。

要按順序運行它,我正在使用條件變量和「notify_one()」函數。

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