C++11中std::condition_variable的使用

原文转载于:https://blog.csdn.net/fengbingchun/article/details/73695596

<condition_variable>是C++标准程序库中的一个头文件,定义了C++11标准中的一些用于并发编程时表示条件变量的类与方法等。

条件变量是并发程序设计中的一种控制结构。多个线程访问一个共享资源(或称临界区)时,不但需要用互斥锁实现独享访问以避免并发错误(称为竞争危害),在获得互斥锁进入临界区后还需要检验特定条件是否成立:

(1)、如果不满足该条件,拥有互斥锁的线程应该释放该互斥锁,把自身阻塞(block)并挂到(suspend)条件变量的线程队列中

(2)、如果满足该条件,拥有互斥锁的线程在临界区内访问共享资源,在退出临界区时通知(notify)在条件变量的线程队列中处于阻塞状态的线程,被通知的线程必须重新申请对该互斥锁加锁。

C++11的标准库中新增加的条件变量的实现,与pthread的实现语义完全一致。使用条件变量做并发控制时,某一时刻阻塞在一个条件变量上的各个线程应该在调用wait操作时指明同一个互斥锁,此时该条件变量与该互斥锁绑定;否则程序的行为未定义。条件变量必须与互斥锁配合使用,其理由是程序需要判定某个条件(condition或称predict)是否成立,该条件可以是任意复杂。

离开临界区的线程用notify操作解除阻塞(unblock)在条件变量上的各个线程时,按照公平性(fairness)这些线程应该有平等的获得互斥锁的机会,不应让某个线程始终难以获得互斥锁被饿死(starvation),并且比后来到临界区的其它线程更为优先(即基本上FIFO)。一种办法是调用了notify_all的线程保持互斥锁,直到所有从条件变量上解除阻塞的线程都已经挂起(suspend)到互斥锁上,然后发起了notify_all的线程再释放互斥锁。互斥锁上一般都有比较完善的阻塞线程调度算法,一般会按照线程优先级调度,相同优先级按照FIFO调度。

发起notify的线程不需要拥有互斥锁。即将离开临界区的线程是先释放互斥锁还是先notify操作解除在条件变量上挂起线程的阻塞?表面看两种顺序都可以。但一般建议是先notify操作,后对互斥锁解锁。因为这既有利于上述的公平性,同时还避免了相反顺序时可能的优先级倒置。这种先notify后解锁的做法是悲观的(pessimization),因为被通知(notified)线程将立即被阻塞,等待通知(notifying)线程释放互斥锁。很多实现(特别是pthreads的很多实现)为了避免这种”匆忙与等待”(hurry up and wait)情形,把在条件变量的线程队列上处于等待的被通知线程直接移到互斥锁的线程队列上,而不唤醒这些线程。

C++11中引入了条件变量,其相关内容均在<condition_variable>中。这里主要介绍std::condition_variable类。

条件变量std::condition_variable用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程。std::condition_variable需要与std::unique_lock配合使用。std::condition_variable效果上相当于包装了pthread库中的pthread_cond_*()系列的函数。

当std::condition_variable对象的某个wait函数被调用的时候,它使用std::unique_lock(通过std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的std::condition_variable对象上调用了notification函数来唤醒当前线程。

std::condition_variable对象通常使用std::unique_lock<std::mutex>来等待,如果需要使用另外的lockable类型,可以使用std::condition_variable_any类。

std::condition_variable类的成员函数:

(1)、构造函数:仅支持默认构造函数,拷贝、赋值和移动(move)均是被禁用的。

(2)、wait:当前线程调用wait()后将被阻塞,直到另外某个线程调用notify_*唤醒当前线程;当线程被阻塞时,该函数会自动调用std::mutex的unlock()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常是另外某个线程调用notify_*唤醒了当前线程),wait()函数也是自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种。

无条件被阻塞:调用该函数前,当前线程应该已经对unique_lock<mutex> lck完成了加锁。所有使用同一个条件变量的线程必须在wait函数中使用同一个unique_lock<mutex>。该wait函数内部会自动调用lck.unlock()对互斥锁解锁,使得其他被阻塞在互斥锁上的线程恢复执行。使用本函数被阻塞的当前线程在获得通知(notified,通过别的线程调用 notify_*系列的函数)而被唤醒后,wait()函数恢复执行并自动调用lck.lock()对互斥锁加锁。

带条件的被阻塞:wait函数设置了谓词(Predicate),只有当pred条件为false时调用该wait函数才会阻塞当前线程,并且在收到其它线程的通知后只有当pred为true时才会被解除阻塞。因此,等效于while (!pred())  wait(lck).

(3)、wait_for:与wait()类似,只是wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其它线程的通知,wait_for返回,剩下的步骤和wait类似。

(4)、wait_until:与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其它线程的通知,wait_until返回,剩下的处理步骤和wait类似。

(5)、notify_all: 唤醒所有的wait线程,如果当前没有等待线程,则该函数什么也不做。

(6)、notify_one:唤醒某个wait线程,如果当前没有等待线程,则该函数什么也不做;如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

条件变化存在虚假唤醒的情况,因此在线程被唤醒后需要检查条件是否满足。无论是notify_one或notify_all都是类似于发出脉冲信号,如果对wait的调用发生在notify之后是不会被唤醒的,所以接收者在使用wait等待之前也需要检查条件是否满足。

std::condition_variable_any类与std::condition_variable用法一样,区别仅在于std::condition_variable_any的wait函数可以接受任何lockable参数,而std::condition_variable只能接受std::unique_lock<std::mutex>类型的参数。

std::notify_all_at_thread_exit函数:当调用该函数的线程退出时,所有在cond条件变量上等待的线程都会收到通知。

std::condition_variable:A condition variable is an object able to block the calling thread until notified to resume. It uses a unique_lock (over a mutex) to lock the thread when one of its wait functions is called. The thread remains blocked until woken up by another thread that calls a notification function on the same condition_variable object. Objects of type condition_variable always use unique_lock<mutex> to wait: for an alternative that works with any kind of lockable type, see condition_variable_any.

The condition_variable class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable.

The thread that intends to modify the variable has to:(1)、acquire a std::mutex (typically via std::lock_guard);(2)、perform the modification while the lock is held;(3)、execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification).

Any thread that intends to wait on std::condition_variable has to:(1)、acquire a std::unique_lock<std::mutex>, on the same mutex as used to protect the shared variable;(2)、execute wait, wait_for, or wait_until. The wait operations atomically release the mutex and suspend the execution of the thread;(3)、When the condition variable is notified, a timeout expires, or a spurious wake up occurs,the thread is awakened, and the mutex is atomically reacquired. The thread should then check the condition and resume waiting if the wake up was spurious.

std::condition_variable works only with std::unique_lock<std::mutex>; this restriction allows for maximal efficiency on some platforms. std::condition_variable_any provides a condition variable that works with any BasicLockable object, such as std::shared_lock.

下面是从其它文章中copy的std::condition_variable测试代码,详细内容介绍可以参考对应的reference:

  1. #include "condition_variable.hpp"
  2. #include <iostream>
  3. #include <chrono>
  4. #include <thread>
  5. #include <mutex>
  6. #include <condition_variable>
  7. #include <string>
  8. namespace condition_variable_ {
  9. //////////////////////////////////////////////////////////////////////
  10. // reference: http://www.cplusplus.com/reference/condition_variable/condition_variable/
  11. std::mutex mtx;
  12. std::condition_variable cv;
  13. bool ready = false;
  14. static void print_id(int id)
  15. {
  16. std::unique_lock< std::mutex> lck(mtx);
  17. while (!ready) cv.wait(lck);
  18. // ...
  19. std:: cout << "thread " << id << '\n';
  20. }
  21. static void go()
  22. {
  23. std::unique_lock< std::mutex> lck(mtx);
  24. ready = true;
  25. cv.notify_all();
  26. }
  27. int test_condition_variable_1()
  28. {
  29. std::thread threads[ 10];
  30. // spawn 10 threads:
  31. for ( int i = 0; i< 10; ++i)
  32. threads[i] = std::thread(print_id, i);
  33. std:: cout << "10 threads ready to race...\n";
  34. go(); // go!
  35. for ( auto& th : threads) th.join();
  36. return 0;
  37. }
  38. /////////////////////////////////////////////////////////////////////////
  39. // reference: http://www.cplusplus.com/reference/condition_variable/condition_variable/wait/
  40. // condition_variable::wait: Wait until notified,
  41. // The execution of the current thread (which shall have locked lck's mutex) is blocked until notified.
  42. // At the moment of blocking the thread, the function automatically calls lck.unlock(), allowing other locked threads to continue.
  43. // If pred is specified, the function only blocks if pred returns false,
  44. // and notifications can only unblock the thread when it becomes true (which is specially useful to check against spurious wake-up calls).
  45. std::mutex mtx2;
  46. std::condition_variable cv2;
  47. int cargo = 0;
  48. static bool shipment_available() { return cargo != 0; }
  49. static void consume(int n)
  50. {
  51. for ( int i = 0; i<n; ++i) {
  52. std::unique_lock< std::mutex> lck(mtx2);
  53. cv2.wait(lck, shipment_available);
  54. // consume:
  55. std:: cout << cargo << '\n';
  56. cargo = 0;
  57. std:: cout << "****: " << cargo << std:: endl;
  58. }
  59. }
  60. int test_condition_variable_wait()
  61. {
  62. std:: thread consumer_thread(consume, 10);
  63. // produce 10 items when needed:
  64. for ( int i = 0; i< 10; ++i) {
  65. while (shipment_available()) std::this_thread::yield();
  66. std::unique_lock< std::mutex> lck(mtx2);
  67. cargo = i + 1;
  68. cv2.notify_one();
  69. }
  70. consumer_thread.join();
  71. return 0;
  72. }
  73. ///////////////////////////////////////////////////////////////////////////
  74. // reference: http://www.cplusplus.com/reference/condition_variable/condition_variable/wait_for/
  75. // condition_variable::wait_for: Wait for timeout or until notified
  76. // The execution of the current thread (which shall have locked lck's mutex) is blocked during rel_time,
  77. // or until notified (if the latter happens first).
  78. // At the moment of blocking the thread, the function automatically calls lck.unlock(),
  79. // allowing other locked threads to continue.
  80. std::condition_variable cv3;
  81. int value;
  82. static void read_value()
  83. {
  84. std:: cin >> value;
  85. cv3.notify_one();
  86. }
  87. int test_condition_variable_wait_for()
  88. {
  89. std:: cout << "Please, enter an integer (I'll be printing dots): \n";
  90. std:: thread th(read_value);
  91. std::mutex mtx;
  92. std::unique_lock< std::mutex> lck(mtx);
  93. while (cv3.wait_for(lck, std::chrono::seconds( 1)) == std::cv_status::timeout) {
  94. std:: cout << '.' << std:: endl;
  95. }
  96. std:: cout << "You entered: " << value << '\n';
  97. th.join();
  98. return 0;
  99. }
  100. //////////////////////////////////////////////////////////////////
  101. // reference: http://www.cplusplus.com/reference/condition_variable/condition_variable/notify_one/
  102. // condition_variable::notify_one: Notify one, Unblocks one of the threads currently waiting for this condition.
  103. // If no threads are waiting, the function does nothing.
  104. // If more than one, it is unspecified which of the threads is selected.
  105. std::mutex mtx4;
  106. std::condition_variable produce4, consume4;
  107. int cargo4 = 0; // shared value by producers and consumers
  108. static void consumer4()
  109. {
  110. std::unique_lock< std::mutex> lck(mtx4);
  111. while (cargo4 == 0) consume4.wait(lck);
  112. std:: cout << cargo4 << '\n';
  113. cargo4 = 0;
  114. produce4.notify_one();
  115. }
  116. static void producer(int id)
  117. {
  118. std::unique_lock< std::mutex> lck(mtx4);
  119. while (cargo4 != 0) produce4.wait(lck);
  120. cargo4 = id;
  121. consume4.notify_one();
  122. }
  123. int test_condition_variable_notify_one()
  124. {
  125. std::thread consumers[ 10], producers[ 10];
  126. // spawn 10 consumers and 10 producers:
  127. for ( int i = 0; i< 10; ++i) {
  128. consumers[i] = std::thread(consumer4);
  129. producers[i] = std::thread(producer, i + 1);
  130. }
  131. // join them back:
  132. for ( int i = 0; i< 10; ++i) {
  133. producers[i].join();
  134. consumers[i].join();
  135. }
  136. return 0;
  137. }
  138. /////////////////////////////////////////////////////////////
  139. // reference: http://www.cplusplus.com/reference/condition_variable/condition_variable/notify_all/
  140. // condition_variable::notify_all: Notify all, Unblocks all threads currently waiting for this condition.
  141. // If no threads are waiting, the function does nothing.
  142. std::mutex mtx5;
  143. std::condition_variable cv5;
  144. bool ready5 = false;
  145. static void print_id5(int id) {
  146. std::unique_lock< std::mutex> lck(mtx5);
  147. while (!ready5) cv5.wait(lck);
  148. // ...
  149. std:: cout << "thread " << id << '\n';
  150. }
  151. static void go5()
  152. {
  153. std::unique_lock< std::mutex> lck(mtx5);
  154. ready5 = true;
  155. cv5.notify_all();
  156. }
  157. int test_condition_variable_notify_all()
  158. {
  159. std::thread threads[ 10];
  160. // spawn 10 threads:
  161. for ( int i = 0; i< 10; ++i)
  162. threads[i] = std::thread(print_id5, i);
  163. std:: cout << "10 threads ready to race...\n";
  164. go5(); // go!
  165. for ( auto& th : threads) th.join();
  166. return 0;
  167. }
  168. ////////////////////////////////////////////////////////////
  169. // reference: http://en.cppreference.com/w/cpp/thread/condition_variable
  170. std::mutex m;
  171. std::condition_variable cv6;
  172. std:: string data;
  173. bool ready6 = false;
  174. bool processed = false;
  175. static void worker_thread()
  176. {
  177. // Wait until main() sends data
  178. std::unique_lock< std::mutex> lk(m);
  179. cv6.wait(lk, []{ return ready6; });
  180. // after the wait, we own the lock.
  181. std:: cout << "Worker thread is processing data\n";
  182. data += " after processing";
  183. // Send data back to main()
  184. processed = true;
  185. std:: cout << "Worker thread signals data processing completed\n";
  186. // Manual unlocking is done before notifying, to avoid waking up
  187. // the waiting thread only to block again (see notify_one for details)
  188. lk.unlock();
  189. cv6.notify_one();
  190. }
  191. int test_condition_variable_2()
  192. {
  193. std:: thread worker(worker_thread);
  194. data = "Example data";
  195. // send data to the worker thread
  196. {
  197. std::lock_guard< std::mutex> lk(m);
  198. ready6 = true;
  199. std:: cout << "main() signals data ready for processing\n";
  200. }
  201. cv6.notify_one();
  202. // wait for the worker
  203. {
  204. std::unique_lock< std::mutex> lk(m);
  205. cv6.wait(lk, []{ return processed; });
  206. }
  207. std:: cout << "Back in main(), data = " << data << '\n';
  208. worker.join();
  209. return 0;
  210. }
  211. } // namespace condition_variable_

GitHub https://github.com/fengbingchun/Messy_Test

猜你喜欢

转载自blog.csdn.net/business122/article/details/80881925