5 Temmuz 2019 Cuma

std::condition_variable Sınıfı

Giriş
Şu satırı dahil ederiz.
#include <condition_variable>
Condition nesnesi unique_lock ile kullanılır. Başka bir lock tipi ile kullanıldığını görmedim.

Constructor
Şöyle yaparız.
condition_variable cv;
notify_all metodu
Nesne üzerinde beklemekte olan tüm thread'leri uyandıdır. Şöyle yaparız.
cv.notify_all();
notify_one metodu
Nesne üzerinde beklemekte olan bir thread'i uyandırır. Şöyle yaparız
cv.notify_one (); // notify one waiting thread
Spurious (Sahte/Yalancı) wakeup
wait(), wait_for() ve türevleri, condition_variable notfiy() veya notify_all() çağrısı almadan da bir sonuç döndürebilirler. Buna Spurious wakeup deniliyor. Dolayısıyla wait() ve türevlerinden döndükten sonra istenilen koşulu kontrol etmek gerekiyor. 

Örnek - Kontrol Etmezsek
Elimizde şöyle bir kod olsun. Burada beklenti thread'in sonsuza kadar dönmesi, ancak öyle olmuyor. Thread, Spurious wakeup ile uyandırılıyor ve çağrı sonucu std::cv_status::timeout yerine std::cv_status::no_timeout geliyor. Bu durumda da thread döngüden çıkıyor.
std::condition_variable cv;
std::mutex mtx;
bool called = false;

void printThread()
{
  std::unique_lock<std::mutex> lck(mtx);
  while (std::cv_status::timeout == cv.wait_for(lck, std::chrono::seconds(1)))
  {
    std::cout << "*";
  }
  std::cout << "thread exits" << std::endl;
}

int main()
{
    std::thread th(printThread);
    th.join();
    std::cout << "program exits" << std::endl;
}
wait metodu - while döngüsü
Metoda girişte mutex'i bırakır çıkışta ise kilitler. Spurious wakeup'a dikkat etmek gerekir. Bu yüzden while döngüsü içinde kullanmak gerekir. 
Örnek
Şöyle yaparız
std::unique_lock <std::mutex> locker (mutex);
while (queue.empty())
{
  cond_.wait(mlock);
}
...
wait metodu - lambda
Açıklaması şöyle. spurious wakeup'a dikkat etmek gerekir. Bu yüzden lamda ile kullanılıyor.
Atomically releases lock, blocks the current executing thread, and adds it to the list of threads waiting on *this. The thread will be unblocked when notify_all() or notify_one() is executed. It may also be unblocked spuriously. When unblocked, regardless of the reason, lock is reacquired and wait exits. If this function exits via exception, lock is also reacquired. (until C++14)
Metoda girişte mutex'i bırakır çıkışta ise kilitler.  Çıkış koşulu lambda'nın true dönmesidir. Bu metod while döngüsü kullanmamızı engeller
Örnek
Şöyle yaparız.
std::mutex m;
std::condition_variable cv;

std::unique_lock<std::mutex> locker (m);
cv.wait(locker,[](){return ...;});
Örnek
Elimizde bir veri yapısı olsun ve istenilen sayıda eleman oluncaya kadar beklemek isteyelim. Şöyle yaparız.
void Foo::extract (const unsigned int n)
{
  // Wait until the buffer is not empty
  std::unique_lock<mutex> locker (m);
  cv.wait (locker, [this, n](){
    return (int)(v.size() - n) >= 0;
  });

  ...

  //Consume. Not full any more
  cv.notify_one();
}
wait_for metodu - timeout + lambda
wait_for() metodu std::this_thread::sleep_for ile aynı gibi görünüyor. Ancak arada bir fark var. condition_variable süre bitmeden önce de uyandırılabilir.  Yani spurious wakeup'a dikkat etmek gerekir. Bu yüzden lamda ile kullanılıyor.

Örnek
Şöyle yaparız.
const std::chrono::milliseconds timeout (3000 );
if(cv.wait_for (locker, timeout, [] {return ...; }) ) {...}
Örnek
Şöyle yaparız.
// Somewhere else, e.g. in a header:
std::mutex mutex;
bool condition_to_be_met{false};
std::condition_variable cv;

// In your timer:
// ...
std::unique_lock<std::mutex> lock{mutex};
if(!cv.wait_for(lock, std::chrono::milliseconds{timeout_ms},
  [this]{return condition_to_be_met;}))
std::cout << "timed out!" << std::endl;
Tetiklemek için şöyle yaparız.
{
  std::lock_guard<std::mutex> lock{mutex}; // Same instance as above!
  condition_to_be_met = true;
}
cv.notify_one();
wait_for metodu - timeout + predicate
While döngüsü içinde kullanmamızı engeller.  spurious wakeup'a dikkat etmek gerekir. Bu yüzden predicate ile kullanılıyor.
Örnek
Şöyle yaparız.
const std::chrono::milliseconds timeout (3000 );
if(cv.wait_for (locker, timeout, myPredicate) {...}
predicate şöyledir.
bool myPredicate(){
  return ...;
}
wait_until metodu
Şöyle yaparız.
auto delay =  ...;
std::unique_lock<std::mutex> lock (m);
cv.wait_until (lock,delay,[] { return ...; });

Diğer Notlar

Barrier Örneği
class Barrier
{
  std::mutex&                lock;
  int                        count;
  std::condition_variable    threadBarrier;

  public:
  Barrier(std::mutex& m, int count) : lock(m), count(count) {}
  void checkin()
  {
    std::unique_lock<std::mutex> locker(lock);
    --count;
    if (count > 0)
    {
      threadBarrier.wait(locker, [&count, this](){
                     return count <= 0;
      });
    }
    else
    {
      threadBarrier.notify_all();
    }
  }
};

Hiç yorum yok:

Yorum Gönder