2014-03-24 21 views
11

我嘗試寫一些多線程代碼從DAQ設備讀取並呈現在同一時間捕捉的信號:爲什麼我的線程有時會「口吃」?

std::atomic <bool> rendering (false); 
auto render = [&rendering, &display, &signal] (void) 
    { 
     while (not rendering) 
      {std::this_thread::yield();}; 
     do {display.draw (signal);} 
      while (display.rendering()); // returns false when user quits 
     rendering = false; 
    }; 
auto capture = [&rendering, &daq] (void) 
    { 
     for (int i = daq.read_frequency(); i --> 0;) 
      daq.record(); // fill the buffer before displaying the signal 
     rendering = true; 
     do {daq.record();} 
      while (rendering); 
     daq.stop(); 
    }; 
std::thread rendering_thread (render); 
std::thread capturing_thread (capture); 

rendering_thread.join(); 
capturing_thread.join(); 

有時這會正常工作,但通常我得到非常糟糕的口吃。我有render()capture()打印在每個循環迭代一行,並再以五色線,以至於紅色爲render(),藍色是capture()

thread execution vs time

左邊的圖是從一個運行更平穩,右情節是從口吃跑步。

我在C具有大致相當於程序中使用OpenMP和表現得一帆風順:

int status = 0; 
#pragma omp parallel num_threads(2) private(tid) shared(status) 
/* READ AND DRAW */ { 
tid = omp_get_thread_num(); 
/* DRAW */ if (tid is 0) { 
    int finished = 0; 
    while (not finished) { 
     #pragma omp critical 
     /* GET JOB STATUS */ { 
      finished = status; 
     } 
     finished = renderDisplay(); 
    } 
    #pragma omp critical 
    /* TERMINATE DISPLAY */ { 
     cvDestroyAllWindows(); 
    } 
    #pragma omp atomic 
    status ++; 
    #pragma omp flush(status) 
} 
/* READ */ if (tid is 1) { 
    int finished = 0; 
    while (not finished) { 
     #pragma omp critical 
     /* GET JOB STATUS */ { 
      finished = status; 
     } 
     captureSignal(); 
    } 
} 
#pragma omp barrier 
} 

至少,無論是C和C++ 11個版本等同於我,但我可以不知道爲什麼在C++ 11版本中發生口吃。

我不能發佈SSCCE因爲daq.*程序都依賴於NI DAQ庫,但它可能是值得注意的是,daq.record()阻止,直到物理設備讀完,和NI DAQ LIB本身派生幾個線程時開始。

我試過在各種配置中實現原子標誌並更改函數調用順序,沒有任何東西似乎有效果。

這裏發生了什麼,我該如何控制它?

更新:提高採樣率的DAQ減輕了這個問題,這使我強烈懷疑這確實與daq.record()是一個阻塞調用有關。

+3

只有實時操作系統才能控制程序的流程。試圖控制和預測線程調度將會失敗,如果不是那種OS的話。 – galop1n

+3

真的有必要讓每個線程阻塞另一個線程嗎?一個是生產者,一個是消費者。應該非常直接地使它無鎖(這基本上就是你使用原子的原子) – Brent

+0

原子在這個上下文中不是鎖,它是線程之間的「開始/停止」信號。生產者只會阻止消費者,直到它爲消費者提供足夠的數據才能顯示有意義的東西。之後,線程獨立工作,直到消費者報告用戶想要退出。 –

回答

1

正如評論中的人們所提到的,您對調度沒有太多的控制。什麼可能可以幫助你更多的是轉離旋轉鎖和使用條件。這會強制渲染線程進入睡眠狀態,如果速度太快並且處理捕獲線程產生的所有數據。您可以查看this example 1次迭代。在你的情況下,每次從捕獲線程獲得更多數據時,都需要調用notify_one()。您可以使用wait版本,該版本僅針對您的案例使用1個參數。

所以你的代碼會變得這樣的事情

std::mutex mutex; 
std::condition_variable condition; 
std::atomic <bool> rendering (false); 
auto render = [&rendering, &display, &signal] (void) 
    { 
     // this while loop is not needed anymore because 
     // we will wait for a signal before doing any drawing 
     while (not rendering) 
      {std::this_thread::yield();}; 
     // first we lock. destructor will unlock for us 
     std::unique_lock<std::mutex> lock(mutex); 
     do { 
       // this will wait until we have been signaled 
       condition.wait(lock); 
       // maybe check display.rendering() and exit (depending on your req.) 
       // process all data available 
       display.draw (signal); 
      } while (display.rendering()); // returns false when user quits 
     rendering = false; 
    }; 
auto capture = [&rendering, &daq] (void) 
    { 
     for (int i = daq.read_frequency(); i --> 0;) 
      daq.record(); // fill the buffer before displaying the signal 
     rendering = true; 
     condition.notify_one(); 
     // special note; you can call notify_one() here with 
     // the mutex lock not acquired. 
     do {daq.record(); condition.notify_one();} 
      while (rendering); 
     daq.stop(); 
     // signal one more time as the render thread could have 
     // been in "wait()" call 
     condition.notify_one(); 
    }; 
std::thread rendering_thread (render); 
std::thread capturing_thread (capture); 

rendering_thread.join(); 
capturing_thread.join(); 

這樣做,這樣也將減少CPU的負擔,因爲渲染線程將進入睡眠狀態時,有沒有數據來處理。

+0

當我回到我的工作電腦時,我會試試這個。我覺得我應該提到渲染線程實際上會讀取緩衝區,而忽略捕獲線程的活動。 (顯示器會撕掉一點,但例程僅用於粗略的可視化調試)。所以從它開始的時候開始,它總是有數據要處理,因爲它會繪製整個緩衝區,直到它獲得「停止」信號爲止......我不確定這是否會改變任何內容(或者是問題的一部分)。 –

+0

你需要在你的線程之間進行某種同步。使用布爾值是最糟糕的方式,因爲它不能幫助調度程序知道你的代碼的意圖是什麼。使用用於同步的基元,它會幫助調度器和代碼的讀者。當你的代碼中有一個「條件」而不是前者依賴於某個其他線程的bool時,這顯然更明顯。 – xryl669

+0

我認爲你在騎自行車。 –

相關問題