5

Nie mogę uzyskać niezawodnie działającego kodu w prostej aplikacji konsoli VS2012, składającej się z producenta i konsumenta, który używa zmiennej warunkowej C++ 11. Jestem zmierzające do produkcji małego niezawodnego programu (do wykorzystania jako podstawa do bardziej kompleksowego programu), który używa 3 argumentu wait_for metody lub być może metodą wait_until z kodem mam zebrane na tych stronach:Używanie zmiennej warunkowej C++ 11 w VS2012

condition_variable: wait_for , wait_until

Chciałbym użyć 3 argumentu wait_for z predykatem jak poniżej, z wyjątkiem tego, że będzie potrzebował użyć zmiennej członka klasy, aby być najbardziej użytecznym dla mnie później. Otrzymuję komunikat "Lokalizacja zapisu naruszenia zasad 0x_ _" lub "Nieprawidłowy parametr został przekazany do usługi lub funkcji" jako błędy po zaledwie około minucie uruchomienia.

Czy steady_clock i 2 argument wait_until będą wystarczające do zastąpienia 3 argument wait_for? Próbowałem tego również bez powodzenia.

Czy ktoś może pokazać, jak uzyskać poniższy kod, aby działał przez czas nieokreślony, bez błędów i dziwnych zachowań w przypadku zmiany czasu zegarowego z czasu letniego lub z synchronizacji czasu w Internecie?

Łącze do wiarygodnego kodu przykładowego może być równie pomocne.

// ConditionVariable.cpp : Defines the entry point for the console application. 
// 

#include "stdafx.h" 

#include <condition_variable> 
#include <mutex> 
#include <thread> 
#include <iostream> 
#include <queue> 
#include <chrono> 
#include <atomic> 

#define TEST1 

std::atomic<int> 
//int 
    qcount = 0; //= ATOMIC_VAR_INIT(0); 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    std::queue<int> produced_nums; 
    std::mutex m; 
    std::condition_variable cond_var; 
    bool notified = false; 
    unsigned int count = 0; 

    std::thread producer([&]() { 
     int i = 0; 
     while (1) { 
      std::this_thread::sleep_for(std::chrono::microseconds(1500)); 
      std::unique_lock<std::mutex> lock(m); 
      produced_nums.push(i); 
      notified = true; 
      qcount = produced_nums.size(); 
      cond_var.notify_one(); 
      i++; 
     } 
     cond_var.notify_one(); 
    }); 

    std::thread consumer([&]() { 
     std::unique_lock<std::mutex> lock(m); 
     while (1) { 
#ifdef TEST1 
      // Version 1 
      if (cond_var.wait_for(
       lock, 
       std::chrono::microseconds(1000), 
       [&]()->bool { return qcount != 0; })) 
      { 
       if ((count++ % 1000) == 0) 
        std::cout << "consuming " << produced_nums.front () << '\n'; 
       produced_nums.pop(); 
       qcount = produced_nums.size(); 
       notified = false; 
      } 
#else 
      // Version 2 
      std::chrono::steady_clock::time_point timeout1 = 
       std::chrono::steady_clock::now() + 
       //std::chrono::system_clock::now() + 
       std::chrono::milliseconds(1); 

      while (qcount == 0)//(!notified) 
      { 
       if (cond_var.wait_until(lock, timeout1) == std::cv_status::timeout) 
        break; 
      } 

      if (qcount > 0) 
      { 
       if ((count++ % 1000) == 0) 
       std::cout << "consuming " << produced_nums.front() << '\n'; 
       produced_nums.pop(); 
       qcount = produced_nums.size(); 
       notified = false; 
      } 
#endif 
     } 
    }); 

    while (1); 
    return 0; 
} 

Program Visual Studio Desktop Express miał jedną ważną aktualizację, którą zainstalował, a usługa Windows Update nie zawiera innych ważnych aktualizacji. Używam 32-bitowego systemu Windows 7.

Odpowiedz

1

Przede wszystkim podczas korzystania condition_variable s ja osobiście wolę niektórych klas otoki jak AutoResetEvent z C#:

struct AutoResetEvent 
{ 
    typedef std::unique_lock<std::mutex> Lock; 

    AutoResetEvent(bool state = false) : 
     state(state) 
    { } 

    void Set() 
    { 
     auto lock = AcquireLock(); 
     state = true; 
     variable.notify_one(); 
    } 

    void Reset() 
    { 
     auto lock = AcquireLock(); 
     state = false; 
    } 

    void Wait(Lock& lock) 
    { 
     variable.wait(lock, [this]() { return this->state; }); 
     state = false; 
    } 

    void Wait() 
    { 
     auto lock = AcquireLock(); 
     Wait(lock); 
    } 

    Lock AcquireLock() 
    { 
     return Lock(mutex); 
    } 
private: 

    bool state; 
    std::condition_variable variable; 
    std::mutex mutex; 
}; 

To nie może być takie samo zachowanie jak typ C# lub może nie być tak skuteczne, jak powinno być ale robi to dla mnie.

Po drugie, gdy potrzebuję wdrożyć idiom produkcji/konsumpcji, staram się użyć współbieżnej implementacji kolejki (np. tbb queue) lub napisać dla siebie. Ale powinieneś także rozważyć poprawne działanie przy użyciu Active Object Pattern. Ale dla prostego rozwiązania możemy użyć tego:

template<typename T> 
struct ProductionQueue 
{ 
    ProductionQueue() 
    { } 

    void Enqueue(const T& value) 
    { 
     { 
      auto lock = event.AcquireLock(); 
      q.push(value); 
     } 
     event.Set(); 
    } 

    std::size_t GetCount() 
    { 
     auto lock = event.AcquireLock(); 

     return q.size(); 
    } 

    T Dequeue() 
    { 
     auto lock = event.AcquireLock(); 
     event.Wait(lock); 

     T value = q.front(); 
     q.pop(); 

     return value; 
    } 

private: 
    AutoResetEvent event; 
    std::queue<T> q; 
}; 

Ta klasa ma kilka kwestii bezpieczeństwa wyjątek i tęskni const-ności na metodach, ale jak powiedziałem, na proste rozwiązanie to powinno pasować.

Więc w wyniku zmodyfikowanego kodu wygląda następująco:

int main(int argc, char* argv[]) 
{ 
    ProductionQueue<int> produced_nums; 
    unsigned int count = 0; 

    std::thread producer([&]() { 
     int i = 0; 
     while (1) { 
      std::this_thread::sleep_for(std::chrono::microseconds(1500)); 
      produced_nums.Enqueue(i); 
      qcount = produced_nums.GetCount(); 
      i++; 
     } 
    }); 

    std::thread consumer([&]() { 
     while (1) { 
      int item = produced_nums.Dequeue(); 
      { 
       if ((count++ % 1000) == 0) 
        std::cout << "consuming " << item << '\n'; 
       qcount = produced_nums.GetCount(); 
      } 
     } 
    }); 

    producer.join(); 
    consumer.join(); 

    return 0; 
} 
+0

Twój kod nie używa 'wait_for' lub' wait_until' i stąd nie rozwiąże problemu PO za. –

4

Niestety, jest to faktycznie błąd w implementacji VS2012 dnia condition_variable i poprawka nie zostanie poprawione w Musisz uaktualnić. do VS2013 po jej wydaniu.

Patrz:

http://connect.microsoft.com/VisualStudio/feedback/details/762560

+0

Czy to nie sprawi, że vs2012 będzie bezużyteczny (gorzej, a szkodliwie) dla dowolnej wielowątkowej aplikacji/biblioteki?Byłbym bardzo zawiedziony przez Microsoft, jeśli jedynym rozwiązaniem, które oferuje, jest po prostu nie używanie własnego produktu (zamiast tego użyj boost condition_variable itp.). Niestety mamy od średniego do dużego codebase na vs2012 i właśnie planowałem ostatnio konwersję na std :: condition_variable, std :: mutex itd. Zamiast boostów. To koniec moich planów. – Mert

+0

Rzeczywiście. MS nie ma motywacji, aby trzymać ludzi na starych wersjach swojego oprogramowania. Kiedy znajdowałem się w podobnej sytuacji jak ty kilka lat temu, byliśmy po prostu zmuszeni do korzystania z alternatyw. – brendanw