2015-08-25 6 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 и сигнализировать конденсатор, поэтому поток цикла не дожидается до вызова 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, и он удерживает этот замок до тех пор, пока он не начнет ждать на конденсате. Основной поток берет блокировку для установки g_stop = true, которую он может выполнять только в то время, когда другой поток ждет.

Это означает, что сейчас возможны только два возможных исполнения. g_stop = true происходит, пока поток ожидает на конденсаторе, и либо он просыпается непосредственно перед вызовом notify_one(), либо просыпается , потому что вызова notify_one(), но в обоих случаях он сразу увидит g_stop == true и остановит цикл.

+0

Ah; Я не понимал, что wait_for() освободит блокировку. Это имеет смысл для меня. Благодаря! Точка, занятая атомарностью g_stop ... Я предполагал, что согласованные обращения к bools будут по умолчанию атомарными по большинству архитектур; что разумное предположение? (Тем не менее, нет никакого вреда в использовании атома вместо этого, и я буду.) – sjmerel

+0

Также .. Любопытно, почему вы выбрали lock_guard вместо unique_lock в своем коде? – sjmerel

+0

Да, ожидая при условии, что переменная освобождает замок и переходит в режим сна. И он делает это атомарно, поэтому нет возможности перекрыть блокировку мьютекса основным потоком, установив «g_stop = true» и отправив уведомление между разблокировкой и ожиданием. Это означает, что вы не пропустите уведомление ... до тех пор, пока условие ожидания (в данном случае логическое становится истинным) происходит под защитой того же мьютекса, что и переменная условия. Это часть того, что @nos упомянуто в комментарии выше о правильном использовании переменной условия. –

 Смежные вопросы

  • Нет связанных вопросов^_^