2016-07-28 3 views
1

Пытаясь расширить в двух моих предыдущих вопросов Move operations for a class with a thread as member variable и Call function inside a lambda passed to a threadC++ поток тупиковый на ожидание состояния

Я не понимая, почему нить делает wait_for является Somtimes не уведомила результаты Wich в тупике. Cppreference говорит о состоянии переменных http://en.cppreference.com/w/cpp/thread/condition_variable/notify_one

Уведомляющая нити не нужно удерживать блокировку на тот же мьютекс, как один, принадлежащей резьба (ами) ожидания; на самом деле это пессимизация, так как уведомляемый поток немедленно блокируется снова, ожидая, что уведомляющий поток освободит блокировку.

MCVE, комментируемая линия объясняет, что изменится, если я держу замок, но я не undrestand почему:

#include <atomic> 
#include <condition_variable> 
#include <mutex> 
#include <thread> 

#include <iostream> 

using namespace std; 

class worker { 
public: 
    template <class Fn, class... Args> 
    explicit worker(Fn func, Args... args) { 
     t = std::thread(
      [&func, this](Args... cargs) -> void { 
       std::unique_lock<std::mutex> lock(mtx); 
       while (true) { 
        cond.wait(lock, [this]() -> bool { return ready; }); 
        if (terminate) { 
         break; 
        } 

        func(cargs...); 
        ready = false; 
       } 
      }, 
      std::move(args)...); 
    } 

    ~worker() { 
     terminate = true; 
     if (t.joinable()) { 
      run_once(); 
      t.join(); 
     } 
    } 

    void run_once() { 
     // If i dont hold this mutex the thread is never notified of ready being 
     // true. 
     std::unique_lock<std::mutex> lock(mtx); 
     ready = true; 
     cout << "ready run once " << ready << endl; 
     cond.notify_all(); 
    } 

    bool done() { return (!ready.load()); } 

private: 
    std::thread t; 
    std::atomic<bool> terminate{false}; 
    std::atomic<bool> ready{false}; 
    std::mutex mtx; 
    std::condition_variable cond; 
}; 

// main.cpp 

void foo() { 
    worker t([]() -> void { cout << "Bark" << endl; }); 
    t.run_once(); 
    while (!t.done()) { 
    } 
} 

int main() { 
    while (true) { 
     foo(); 
    } 
    return 0; 
} 
+1

Это «ready = true:», которое должно произойти под мьютексом. Не имеет значения, что это атомный. – Cubbi

+0

@ user2079303 Исправлено! Тем не менее его трудно увидеть, если исполнение «истекает». – Aram

+0

@ user2079303 Это не мгновенный тупик, по крайней мере, на моем компьютере. Вот почему я поставил цикл, чтобы я мог вызвать функцию несколько раз. – Aram

ответ

1

Вам нужен барьер памяти, чтобы убедиться, что другой поток будет видеть модифицированный " готовое "значение. «ready», будучи атомарным, гарантирует, что доступ к памяти упорядочен так, что изменения, которые произошли до атомного доступа, фактически были сброшены в основную память. Это не гарантирует, что другие потоки будут видеть эту память, поскольку эти потоки могут иметь свой собственный кеш памяти. Поэтому, чтобы убедиться, что другой поток видит «готовую» модификацию, требуется мьютекс.

{ 
    std::unique_lock<std::mutex> lock(mtx); 
    ready = true; 
} 
+1

Метод 'load'' std :: atomic' имеет параметр для определения видимости атомных значений по потокам. Из документации для метода 'load': если атомный магазин в потоке A помечен memory_order_release и атомная нагрузка в потоке B из той же переменной помечена mem_order_acquire, вся память записывает (неатомный и расслабленный атом), который произошел раньше хранилище атомов с точки зрения потока A, становятся видимыми побочными эффектами в потоке B, то есть после завершения атомной нагрузки в потоке B гарантируется, что все потоки A будут записаны в память. – keith

+1

Причина в правильности ответа неверна. Как указывает Кейт, атомарность гарантирует, что запись * * фактически видна в другом потоке. Проблема не в кешировании, а в гонке с порядком планирования разных потоков. Решение правильное. – user2079303

-2
std::unique_lock<std::mutex> lock(mtx); 
while (true) { 

область замка мьютекса слишком велик, что делает следующую строку не может никогда не казнили.

std::unique_lock<std::mutex> lock(mtx); 
ready = true; 

На самом деле ваш код не требует наличия мьютекса, поскольку все переменные являются атомарными. Удаление мьютекса должно решить проблему

+0

Извините, но вы не понимаете, как работает переменная условия. Он фактически разблокирует мьютекс. –

+0

, но cond в run_once никогда не исполнялся .... –