2015-08-25 77 views
1

我有一個上傳文件的後臺線程。它運行在一個循環中;它做了一些工作,然後睡覺,直到超時過去,或直到通過條件變量明確通知需要做更多工作。問題是,有時我無法讓線程快速退出。如何退出後臺線程循環?

下面是一個簡化版本:

std::thread g_thread; 
    std::mutex g_mutex; 
    std::condition_variable g_cond; 
    bool g_stop = false; 

    void threadLoop() 
    { 
     while (!g_stop) 
     { 
      printf("doing some stuff\n"); 
      std::unique_lock<std::mutex> lock(g_mutex); 
      g_cond.wait_for(lock, std::chrono::seconds(15)); 
     } 
    } 

    int main(int argc, char* argv[]) 
    {   
     g_stop = false; 
     g_thread = std::thread(threadLoop); 

     printf("hello\n"); 

     g_stop = true; 
     g_cond.notify_one(); 
     g_thread.join(); 
    } 

當我運行這個測試程序,我希望它很快退出,但有時卡在wait_for()。我想也許notify_one()在線程在wait_for()中睡眠之前發生,但在g_stop檢查之後。

有沒有一個簡單的解決方案,或其他設計模式,會更好?

+2

您應該將g_stop的類型更改爲'atomic_bool',以避免**未定義的行爲**,從不介意緩存問題。或者只在持有互斥鎖的同時閱讀它。 –

+0

我不確定C++中的方法,但基本上當線程處於睡眠狀態時,使其響應的唯一方法是發送一個信號。在C中,它將使用'int pthread_kill(pthread_t thread,int sig)'; – AxFab

+0

@AxFab,不,這沒有幫助。它正在等待條件變量,所以你通過通知該條件變量來解鎖它。 –

回答

4

您正在讀寫g_stop變量而沒有任何同步(例如使用原子操作或使用互斥鎖序列化訪問)。這是一個數據競爭,這是未定義的行爲。

因爲你沒有安全地訪問它,所以編譯器可以假定沒有其他線程曾經修改g_stop,所以在threadLoop函數中它可以將它加載到一個寄存器中,然後再也不會再讀取該變量,而只是保持循環。

爲確保在循環線程中可以看到寫入變量,您應該使用std::atomic<bool>或在對所有變量進行讀/寫操作之前鎖定該互斥鎖。如果使用將修復未定義行爲的atomic<bool>,但不確保線程不會等待條件變量,因爲您建議在檢查g_stop的值和進入睡眠之間存在一個窗口,其中主線程可以設置g_stop = true併發出condvar信號,因此循環線程不會等到notify_one()調用之後,因此會錯過它。

這稍微改變版本將確保線程不會在條件變量上等待,如果主線程告訴它停下來:

std::thread g_thread; 
std::mutex g_mutex; 
std::condition_variable g_cond; 
bool g_stop = false; 

void threadLoop() 
{ 
    std::unique_lock<std::mutex> lock(g_mutex); 
    while (!g_stop) 
    { 
     printf("doing some stuff\n"); 
     g_cond.wait_for(lock, std::chrono::seconds(15)); 
    } 
} 

int main(int argc, char* argv[]) 
{   
    g_stop = false; 
    g_thread = std::thread(threadLoop); 

    printf("hello\n"); 

    { 
     std::lock_guard<std::mutex> lock(g_mutex); 
     g_stop = true; 
    } 
    g_cond.notify_one(); 
    g_thread.join(); 
} 

這工作,因爲循環線程持有互斥而鎖它會檢查g_stop,它會一直持有該鎖,直到它開始在condvar上等待。主線程使用鎖來設置g_stop = true,它只能在另一個線程正在等待時執行。

這意味着現在只有兩種可能的執行方式。 g_stop = true發生在線程正在等待condvar時,或者它在notify_one()調用之前醒來,或者由於notify_one()調用的而被喚醒,但是在兩種情況下,都將立即看到g_stop == true並停止循環。

+0

啊;我沒有意識到wait_for()會釋放鎖。這對我有意義。謝謝! 關於g_stop的原子性的觀點...我假設在大多數體系結構中默認情況下對齊的布爾訪問將是原子的;這是一個合理的假設嗎? (即使這樣,使用原子也沒有什麼壞處,我會的。) – sjmerel

+0

另外..好奇的是,爲什麼在代碼中選擇lock_guard而不是unique_lock? – sjmerel

+0

是的,等待條件變量釋放鎖並進入睡眠狀態。而且它原子化,因此主線程不會重新鎖定互斥鎖,設置'g_stop = true'並在解鎖和等待之間發送通知。這意味着你不會錯過通知......只要等待的條件(在這種情況下布爾成爲真)在條件變量等待的同一互斥體的保護下發生。這是@nos在上面的註釋中提到的有關正確使用條件變量的一部分。 –