C++11 并发编程

C++11 新标准中引入了几个头文件来支持多线程编程,他们分别是:

  • <atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
  • <thread>:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
  • <mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
  • <condition_variable>:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
  • <future>:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。

std::thread

构造函数

default (1)
  1. thread() noexcept;
initialization (2)
  1. template <class Fn, class... Args>
  2. explicit thread (Fn&& fn, Args&&... args);
copy [deleted] (3)
  1. thread (const thread&) = delete;
move (4)
  1. thread (thread&& x) noexcept;
  • (1). 默认构造函数,创建一个空的 thread 执行对象。
  • (2). 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
  • (3). 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
  • (4). move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
  • 注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

thread使用例子

  1. #include <iostream>
  2. #include <utility>
  3. #include <thread>
  4. #include <chrono>
  5. #include <functional>
  6. #include <atomic>
  7.  
  8. void f1(int n)
  9. {
  10. for (int i = ; i < ; ++i) {
  11. std::cout << "Thread " << n << " executing\n";
  12. std::this_thread::sleep_for(std::chrono::milliseconds());
  13. }
  14. }
  15.  
  16. void f2(int& n)
  17. {
  18. for (int i = ; i < ; ++i) {
  19. std::cout << "Thread 2 executing\n";
  20. ++n;
  21. std::this_thread::sleep_for(std::chrono::milliseconds());
  22. }
  23. }
  24.  
  25. int main()
  26. {
  27. int n = ;
  28. std::thread t1; // t1 is not a thread
  29. std::thread t2(f1, n + ); // pass by value
  30. std::thread t3(f2, std::ref(n)); // pass by reference
  31. std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
  32. t2.join();
  33. t4.join();
  34. std::cout << "Final value of n is " << n << '\n';
  35. }

std::mutex

Mutex 类

  • std::mutex,最基本的 Mutex 类,如果当前线程对同一个mutex多次加锁,会产生死锁(dead lock);
  • std::recursive_mutex,递归锁,允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,。
  • std::time_mutex,定时 Mutex 类。
  • std::recursive_timed_mutex,定时递归 Mutex 类。

Lock 类

  • std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  • std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

其他类型

  • std::once_flag,配合std:call_once使用;
  • std::adopt_lock_t,通常作为参数传入给 unique_lock 或 lock_guard 的构造函数;
  • std::defer_lock_t,通常作为参数传入给 unique_lock 的构造函数;
  • std::try_to_lock_t,通常作为参数传入给 unique_lock 的构造函数;

函数

  • std::try_lock,调用时没有获得锁,则直接返回 false
  • std::lock,调用线程将阻塞等待该互斥量。
  • std::try_lock_for,对定时锁可用,接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时则返回 false;
  • std::try_lock_util,对定时锁可用,接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时则返回 false;
  • std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

定时锁例子

  1. #include <iostream> // std::cout
  2. #include <chrono> // std::chrono::milliseconds
  3. #include <thread> // std::thread
  4. #include <mutex> // std::timed_mutex
  5.  
  6. std::timed_mutex mtx;
  7.  
  8. void fireworks() {
  9. // waiting to get a lock: each thread prints "-" every 200ms:
  10. while (!mtx.try_lock_for(std::chrono::milliseconds())) {
  11. std::cout << "-";
  12. }
  13. // got a lock! - wait for 1s, then this thread prints "*"
  14. std::this_thread::sleep_for(std::chrono::milliseconds());
  15. std::cout << "*\n";
  16. mtx.unlock();
  17. }
  18.  
  19. int main ()
  20. {
  21. std::thread threads[];
  22. // spawn 10 threads:
  23. for (int i=; i<; ++i)
  24. threads[i] = std::thread(fireworks);
  25.  
  26. for (auto& th : threads) th.join();
  27.  
  28. return ;
  29. }

lock_guard例子

用RAII思想管理锁,不用手动释放

在 lock_guard 对象构造时,传入的 Mutex 对象会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。

值得注意的是,lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。

一个简单例子

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <mutex> // std::mutex, std::lock_guard
  4. #include <stdexcept> // std::logic_error
  5.  
  6. std::mutex mtx;
  7.  
  8. void print_even (int x) {
  9. if (x%==) std::cout << x << " is even\n";
  10. else throw (std::logic_error("not even"));
  11. }
  12.  
  13. void print_thread_id (int id) {
  14. try {
  15. // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
  16. std::lock_guard<std::mutex> lck (mtx);
  17. print_even(id);
  18. }
  19. catch (std::logic_error&) {
  20. std::cout << "[exception caught]\n";
  21. }
  22. }
  23.  
  24. int main ()
  25. {
  26. std::thread threads[];
  27. // spawn 10 threads:
  28. for (int i=; i<; ++i)
  29. threads[i] = std::thread(print_thread_id,i+);
  30.  
  31. for (auto& th : threads) th.join();
  32.  
  33. return ;
  34. }

adopting初始化,对一个已经加锁的mutex使用lock_guard,由其负责解锁

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <mutex> // std::mutex, std::lock_guard, std::adopt_lock
  4.  
  5. std::mutex mtx; // mutex for critical section
  6.  
  7. void print_thread_id (int id) {
  8. mtx.lock();
  9. std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
  10. std::cout << "thread #" << id << '\n';
  11. }
  12.  
  13. int main ()
  14. {
  15. std::thread threads[];
  16. // spawn 10 threads:
  17. for (int i=; i<; ++i)
  18. threads[i] = std::thread(print_thread_id,i+);
  19.  
  20. for (auto& th : threads) th.join();
  21.  
  22. return ;
  23. }

lock_guard只能保证在析构的时候执行解锁操作,本身并没有提供加锁和解锁的接口,看下面的例子

  1. class LogFile {
  2. std::mutex _mu;
  3. ofstream f;
  4. public:
  5. LogFile() {
  6. f.open("log.txt");
  7. }
  8. ~LogFile() {
  9. f.close();
  10. }
  11. void shared_print(string msg, int id) {
  12. {
  13. std::lock_guard<std::mutex> guard(_mu);
  14. //do something 1
  15. }
  16. //do something 2
  17. {
  18. std::lock_guard<std::mutex> guard(_mu);
  19. // do something 3
  20. f << msg << id << endl;
  21. cout << msg << id << endl;
  22. }
  23. }
  24.  
  25. };
上面的代码中,一个函数内部有两段代码需要进行保护,这个时候使用lock_guard就需要创建两个局部对象来管理同一个互斥锁(其实也可以只创建一个,但是锁的力度太大,效率不行),修改方法是使用unique_lock

unique_lock例子

它提供了lock()unlock()接口,能记录现在处于上锁还是没上锁状态,在析构的时候,会根据当前状态来决定是否要进行解锁(lock_guard就一定会解锁)。

  1. class LogFile {
  2. std::mutex _mu;
  3. ofstream f;
  4. public:
  5. LogFile() {
  6. f.open("log.txt");
  7. }
  8. ~LogFile() {
  9. f.close();
  10. }
  11. void shared_print(string msg, int id) {
  12.  
  13. std::unique_lock<std::mutex> guard(_mu);
  14. //do something 1
  15. guard.unlock(); //临时解锁
  16.  
  17. //do something 2
  18.  
  19. guard.lock(); //继续上锁
  20. // do something 3
  21. f << msg << id << endl;
  22. cout << msg << id << endl;
  23. // 结束时析构guard会临时解锁
  24. // 这句话可要可不要,不写,析构的时候也会自动执行
  25. // guard.ulock();
  26. }
  27.  
  28. };
上面的代码可以看到,在无需加锁的操作时,可以先临时释放锁,然后需要继续保护的时候,可以继续上锁,这样就无需重复的实例化lock_guard对象,还能减少锁的区域。
 
 
adopting 初始化,新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)
deferred 初始化,新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象

try-locking 初始化,新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <mutex> // std::mutex, std::lock, std::unique_lock
  4. // std::adopt_lock, std::defer_lock
  5. std::mutex foo,bar;
  6.  
  7. void task_a () {
  8. std::lock (foo,bar); // simultaneous lock (prevents deadlock)
  9. std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock);
  10. std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock);
  11. std::cout << "task a\n";
  12. // (unlocked automatically on destruction of lck1 and lck2)
  13. }
  14.  
  15. void task_b () {
  16. // foo.lock(); bar.lock(); // replaced by:
  17. std::unique_lock<std::mutex> lck1, lck2;
  18. lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock);
  19. lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock);
  20. std::lock (lck1,lck2); // simultaneous lock (prevents deadlock)
  21. std::cout << "task b\n";
  22. // (unlocked automatically on destruction of lck1 and lck2)
  23. }
  24.  
  25. int main ()
  26. {
  27. std::thread th1 (task_a);
  28. std::thread th2 (task_b);
  29.  
  30. th1.join();
  31. th2.join();
  32.  
  33. return ;
  34. }

后面在学习条件变量的时候,还会有unique_lock的用武之地。

call_once例子

若调用发生异常,不会翻转flag,以令其它调用得到尝试

  1. #include <iostream>
  2. #include <thread>
  3. #include <mutex>
  4.  
  5. std::once_flag flag1, flag2;
  6.  
  7. void simple_do_once()
  8. {
  9. std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
  10. }
  11.  
  12. void may_throw_function(bool do_throw)
  13. {
  14. if (do_throw) {
  15. std::cout << "throw: call_once will retry\n"; // 这会出现多于一次
  16. throw std::exception();
  17. }
  18. std::cout << "Didn't throw, call_once will not attempt again\n"; // 保证一次
  19. }
  20.  
  21. void do_once(bool do_throw)
  22. {
  23. try {
  24. std::call_once(flag2, may_throw_function, do_throw);
  25. }
  26. catch (...) {
  27. }
  28. }
  29.  
  30. int main()
  31. {
  32. std::thread st1(simple_do_once);
  33. std::thread st2(simple_do_once);
  34. std::thread st3(simple_do_once);
  35. std::thread st4(simple_do_once);
  36. st1.join();
  37. st2.join();
  38. st3.join();
  39. st4.join();
  40.  
  41. std::thread t1(do_once, true);
  42. std::thread t2(do_once, true);
  43. std::thread t3(do_once, false);
  44. std::thread t4(do_once, true);
  45. t1.join();
  46. t2.join();
  47. t3.join();
  48. t4.join();
  49. }

std::future

<future> 头文件中包含了以下几个类和函数:

  • Providers 类:std::promise, std::package_task
  • Futures 类:std::future, shared_future
  • Providers 函数:std::async()
  • 其他类型:std::future_error, std::future_errc, std::future_status, std::launch

futurepromise的作用是在不同线程之间传递数据,大概流程如下

流程:

  1. 线程1初始化一个promise对象和一个future对象,并将promise传递给线程2,相当于线程2对线程1的一个承诺;future相当于一个接受一个承诺,用来获取未来线程2传递的值;
  2. 线程2获取到promise后,需要对这个promise传递有关的数据,之后线程1的future就可以获取数据了;
  3. 如果线程1想要获取数据,而线程2未给出数据,则线程1阻塞,直到线程2的数据到达

一个例子:

  1. #include <iostream>
  2. #include <functional>
  3. #include <future>
  4. #include <thread>
  5. #include <chrono>
  6. #include <cstdlib>
  7.  
  8. void thread_set_promise(std::promise<int>& promiseObj) {
  9. std::cout << "In a thread, making data...\n";
  10. std::this_thread::sleep_for(std::chrono::milliseconds());
  11. promiseObj.set_value();
  12. std::cout << "Finished\n";
  13. }
  14.  
  15. int main() {
  16. std::promise<int> promiseObj;
  17. std::future<int> futureObj = promiseObj.get_future();
  18. std::thread t(&thread_set_promise, std::ref(promiseObj));
  19. std::cout << futureObj.get() << std::endl;
  20. t.join();
  21.  
  22. return ;
  23. }

promise例子

在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。

future对象的成员函数:

  • std::promise::get_future,返回一个与 promise 共享状态相关联的 future ,返回的 future 对象可以访问由 promise 对象设置在共享状态上的值或者某个异常对象,如果不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常;
  • std::promise::set_value,设置共享状态的值,此后 promise 的共享状态标志变为 ready;
  • std::promise::set_exception,为 promise 设置异常,此后 promise 的共享状态变标志变为 ready;
  • std::promise::set_value_at_thread_exit,设置共享状态的值,但是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready;

设置异常的例子:

  1. #include <iostream> // std::cin, std::cout, std::ios
  2. #include <functional> // std::ref
  3. #include <thread> // std::thread
  4. #include <future> // std::promise, std::future
  5. #include <exception> // std::exception, std::current_exception
  6.  
  7. void get_int(std::promise<int>& prom) {
  8. int x;
  9. std::cout << "Please, enter an integer value: ";
  10. std::cin.exceptions (std::ios::failbit); // throw on failbit
  11. try {
  12. std::cin >> x; // sets failbit if input is not int
  13. prom.set_value(x);
  14. } catch (std::exception&) {
  15. prom.set_exception(std::current_exception());
  16. }
  17. }
  18.  
  19. void print_int(std::future<int>& fut) {
  20. try {
  21. int x = fut.get();
  22. std::cout << "value: " << x << '\n';
  23. } catch (std::exception& e) {
  24. std::cout << "[exception caught: " << e.what() << "]\n";
  25. }
  26. }
  27.  
  28. int main ()
  29. {
  30. std::promise<int> prom;
  31. std::future<int> fut = prom.get_future();
  32.  
  33. std::thread th1(get_int, std::ref(prom));
  34. std::thread th2(print_int, std::ref(fut));
  35.  
  36. th1.join();
  37. th2.join();
  38. return ;
  39. }

package_task例子

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

packaged_task对象的成员函数:

  • std::packaged_task::valid,检查当前 packaged_task 是否和一个有效的共享状态相关联;
  • std::packaged_task::get_future,来获取与共享状态相关联的 std::future 对象;
  • std::packaged_task::make_ready_at_thread_exit,
  • std::packaged_task::reset(),重置 packaged_task 的共享状态,但是保留之前的被包装的任务

package_task使用例子

  1. #include <iostream> // std::cout
  2. #include <future> // std::packaged_task, std::future
  3. #include <chrono> // std::chrono::seconds
  4. #include <thread> // std::thread, std::this_thread::sleep_for
  5.  
  6. // count down taking a second for each value:
  7. int countdown (int from, int to) {
  8. for (int i=from; i!=to; --i) {
  9. std::cout << i << '\n';
  10. std::this_thread::sleep_for(std::chrono::seconds());
  11. }
  12. std::cout << "Finished!\n";
  13. return from - to;
  14. }
  15.  
  16. int main ()
  17. {
  18. std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
  19. std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.
  20.  
  21. std::thread(std::move(task), , ).detach(); //创建一个新线程完成计数任务.
  22.  
  23. int value = ret.get(); // 等待任务完成并获取结果.
  24.  
  25. std::cout << "The countdown lasted for " << value << " seconds.\n";
  26.  
  27. return ;
  28. }

reset 的例子

  1. #include <iostream> // std::cout
  2. #include <utility> // std::move
  3. #include <future> // std::packaged_task, std::future
  4. #include <thread> // std::thread
  5.  
  6. // a simple task:
  7. int triple (int x) { return x*; }
  8.  
  9. int main ()
  10. {
  11. std::packaged_task<int(int)> tsk (triple); // package task
  12.  
  13. std::future<int> fut = tsk.get_future();
  14. std::thread (std::move(tsk), ).detach();
  15. std::cout << "The triple of 100 is " << fut.get() << ".\n";
  16.  
  17. // re-use same task object:
  18. tsk.reset();
  19. fut = tsk.get_future();
  20. std::thread(std::move(tsk), ).detach();
  21. std::cout << "Thre triple of 200 is " << fut.get() << ".\n";
  22.  
  23. return ;
  24. }

future例子

std::future 可以用来获取异步任务的结果。

std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值,如果共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。

一个有效(valid)的 std::future 对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,其实我们前面都已经提到了,他们分别是:

  • std::async 函数;
  • std::promise::get_future,get_future 为 promise 类的成员函数;
  • std::packaged_task::get_future,此时 get_future为 packaged_task 的成员函数;

async 函数使用例子

  1. // future example
  2. #include <iostream> // std::cout
  3. #include <future> // std::async, std::future
  4. #include <chrono> // std::chrono::milliseconds
  5.  
  6. // a non-optimized way of checking for prime numbers:
  7. bool
  8. is_prime(int x)
  9. {
  10. for (int i = ; i < x; ++i)
  11. if (x % i == )
  12. return false;
  13. return true;
  14. }
  15.  
  16. int
  17. main()
  18. {
  19. // call function asynchronously:
  20. std::future < bool > fut = std::async(is_prime, );
  21.  
  22. // do something while waiting for function to set future:
  23. std::cout << "checking, please wait";
  24. std::chrono::milliseconds span();
  25. while (fut.wait_for(span) == std::future_status::timeout)
  26. std::cout << '.';
  27.  
  28. bool x = fut.get(); // retrieve return value
  29.  
  30. std::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";
  31.  
  32. return ;
  33. }

std::async() 返回一个 std::future 对象,通过该对象可以获取异步任务的值或异常(如果异步任务抛出了异常)

另外,async 函数可以指定启动策略 std::launch ,该枚举参数可以是launch::async,launch::deferred,以及两者的按位或( | );

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. #include <cmath>
  5. #include <chrono>
  6. #include <future>
  7. #include <iostream>
  8.  
  9. double ThreadTask(int n) {
  10. std::cout << std::this_thread::get_id()
  11. << " start computing..." << std::endl;
  12.  
  13. double ret = ;
  14. for (int i = ; i <= n; i++) {
  15. ret += std::sin(i);
  16. }
  17.  
  18. std::cout << std::this_thread::get_id()
  19. << " finished computing..." << std::endl;
  20. return ret;
  21. }
  22.  
  23. int main(int argc, const char *argv[])
  24. {
  25. std::future<double> f(std::async(std::launch::async, ThreadTask, ));
  26.  
  27. #if 0
  28. while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds())
  29. != std::future_status::ready) {
  30. std::cout << "task is running...\n";
  31. }
  32. #else
  33. while(f.wait_for(std::chrono::seconds())
  34. != std::future_status::ready) {
  35. std::cout << "task is running...\n";
  36. }
  37. #endif
  38.  
  39. std::cout << f.get() << std::endl;
  40.  
  41. return EXIT_SUCCESS;
  42. }

std::launch枚举类型主要是在调用 std::async 设置异步任务的启动策略的。

类型 描述
launch::async Asynchronous: 异步任务会在另外一个线程中调用,并通过共享状态返回异步任务的结果(一般是调用 std::future::get() 获取异步任务的结果)。
launch::deferred Deferred: 异步任务将会在共享状态被访问时调用,相当与按需调用(即延迟(deferred)调用)。

future对象的成员函数:

  • std::future::valid(),检查当前的 std::future 对象是否有效;
  • std::future::get(),调用该函数会阻塞当前的调用者,而此后一旦共享状态的标志变为 ready,get 返回 Provider 所设置的共享状态的值或者异常(如果抛出了异常);
  • std::future::share(),返回一个 std::shared_future 对象,调用该函数之后,该 std::future 对象本身已经不和任何共享状态相关联,因此该 std::future 的状态不再是 valid 的了;
  • std::future::wait(),等待与该 std::future 对象相关联的共享状态的标志变为 ready,但是 wait() 并不读取共享状态的值或者异常;
  • std::future::wait_for(),可以设置一个时间段 rel_time,如果共享状态的标志在该时间段结束之前没有被 Provider 设置为 ready,则调用 wait_for 的线程被阻塞,在等待了 rel_time 的时间长度后 wait_until() 返回;
  • std::future::wait_until(),可以设置一个系统绝对时间点 abs_time,如果共享状态的标志在该时间点到来之前没有被 Provider 设置为 ready,则调用 wait_until 的线程被阻塞,在 abs_time 这一时刻到来之后 wait_for() 返回;

valid 使用例子

  1. #include <iostream> // std::cout
  2. #include <future> // std::async, std::future
  3. #include <utility> // std::move
  4.  
  5. int do_get_value() { return ; }
  6.  
  7. int main ()
  8. {
  9. // 由默认构造函数创建的 std::future 对象,
  10. // 初始化时该 std::future 对象处于为 invalid 状态.
  11. std::future<int> foo, bar;
  12. foo = std::async(do_get_value); // move 赋值, foo 变为 valid.
  13. bar = std::move(foo); // move 赋值, bar 变为 valid, 而 move 赋值以后 foo 变为 invalid.
  14.  
  15. if (foo.valid())
  16. std::cout << "foo's value: " << foo.get() << '\n';
  17. else
  18. std::cout << "foo is not valid\n";
  19.  
  20. if (bar.valid())
  21. std::cout << "bar's value: " << bar.get() << '\n';
  22. else
  23. std::cout << "bar is not valid\n";
  24.  
  25. return ;
  26. }

wait_for 使用例子

  1. #include <iostream> // std::cout
  2. #include <future> // std::async, std::future
  3. #include <chrono> // std::chrono::milliseconds
  4.  
  5. // a non-optimized way of checking for prime numbers:
  6. bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
  7. {
  8. for (int i = ; i < x; ++i)
  9. if (x % i == )
  10. return false;
  11. return true;
  12. }
  13.  
  14. int main()
  15. {
  16. // call function asynchronously:
  17. std::future < bool > fut = std::async(do_check_prime, );
  18.  
  19. std::cout << "Checking...\n";
  20. std::chrono::milliseconds span(); // 设置超时间隔.
  21.  
  22. // 如果超时,则输出".",继续等待
  23. while (fut.wait_for(span) == std::future_status::timeout)
  24. std::cout << '.';
  25.  
  26. std::cout << "\n194232491 ";
  27. if (fut.get()) // guaranteed to be ready (and not block) after wait returns
  28. std::cout << "is prime.\n";
  29. else
  30. std::cout << "is not prime.\n";
  31.  
  32. return ;
  33. }

shared_future例子

shared_future支持拷贝,多个 std::shared_future 可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。;


std::condition_variable

先看一个例子

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <mutex> // std::mutex, std::unique_lock
  4. #include <condition_variable> // std::condition_variable
  5.  
  6. std::mutex mtx; // 全局互斥锁.
  7. std::condition_variable cv; // 全局条件变量.
  8. bool ready = false; // 全局标志位.
  9.  
  10. void do_print_id(int id)
  11. {
  12. std::unique_lock <std::mutex> lck(mtx);
  13. while (!ready) // 如果标志位不为 true, 则等待...
  14. cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
  15. // 线程被唤醒, 继续往下执行打印线程编号id.
  16. std::cout << "thread " << id << '\n';
  17. }
  18.  
  19. void go()
  20. {
  21. std::unique_lock <std::mutex> lck(mtx);
  22. ready = true; // 设置全局标志位为 true.
  23. cv.notify_all(); // 唤醒所有线程.
  24. }
  25.  
  26. int main()
  27. {
  28. std::thread threads[];
  29. // spawn 10 threads:
  30. for (int i = ; i < ; ++i)
  31. threads[i] = std::thread(do_print_id, i);
  32.  
  33. std::cout << "10 threads ready to race...\n";
  34. go(); // go!
  35.  
  36. for (auto & th:threads)
  37. th.join();
  38.  
  39. return ;
  40. }

wait函数执行的步骤:

  • unlock mutex,wait调用要和mutex配合,调用wait前要先获取mutex的锁,调用wait时会先自动解锁,使得其他被阻塞在锁竞争上的线程得以继续执行。
  • waiting for notify,阻塞等待唤醒;
  • waked by notify,被唤醒;
  • lock mutex,自动重新加锁,使得mutex状态和wait被调用时相同;

另外,上面的代码中,

  1. while (!ready) // 如果标志位不为 true, 则等待...
  2. cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后, 线程被唤醒, 继续往下执行打印线程编号id.

可以用下面的语句替换:

  1. cv.wait(lck, isReady);
  2.  
  3. // isReady的实现
  4. bool isReady() {
  5. return ready;
  6. }

更多wait函数

  1. // wait
  2. void wait (unique_lock<mutex>& lck);
  3. template <class Predicate>
  4. void wait (unique_lock<mutex>& lck, Predicate pred);
  5.  
  6. // wait_for
  7. template <class Rep, class Period>
  8. cv_status wait_for (unique_lock<mutex>& lck,
  9. const chrono::duration<Rep,Period>& rel_time);
  10. template <class Rep, class Period, class Predicate>
  11. bool wait_for (unique_lock<mutex>& lck,
  12. const chrono::duration<Rep,Period>& rel_time, Predicate pred);
  13.  
  14. // wait_until
  15. template <class Clock, class Duration>
  16. cv_status wait_until (unique_lock<mutex>& lck,
  17. const chrono::time_point<Clock,Duration>& abs_time);
  18. template <class Clock, class Duration, class Predicate>
  19. bool wait_until (unique_lock<mutex>& lck,
  20. const chrono::time_point<Clock,Duration>& abs_time,
  21. Predicate pred);

wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态;

wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time 超时之前,该线程都会处于阻塞状态;

wait_for 例子

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <chrono> // std::chrono::seconds
  4. #include <mutex> // std::mutex, std::unique_lock
  5. #include <condition_variable> // std::condition_variable, std::cv_status
  6.  
  7. std::condition_variable cv;
  8.  
  9. int value;
  10.  
  11. void do_read_value()
  12. {
  13. std::cin >> value;
  14. cv.notify_one();
  15. }
  16.  
  17. int main ()
  18. {
  19. std::cout << "Please, enter an integer (I'll be printing dots): \n";
  20. std::thread th(do_read_value);
  21.  
  22. std::mutex mtx;
  23. std::unique_lock<std::mutex> lck(mtx);
  24. while (cv.wait_for(lck,std::chrono::seconds()) == std::cv_status::timeout) {
  25. std::cout << '.';
  26. std::cout.flush();
  27. }
  28.  
  29. std::cout << "You entered: " << value << '\n';
  30.  
  31. th.join();
  32. return ;
  33. }

上面的例子使用了std::cv_status枚举类型:

cv_status::no_timeout wait_for 或者 wait_until 没有超时,即在规定的时间段内线程收到了通知。
cv_status::timeout wait_for 或者 wait_until 超时。

notify函数

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

notify_all_at_thread_exit

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

例子:

  1. #include <iostream> // std::cout
  2. #include <thread> // std::thread
  3. #include <mutex> // std::mutex, std::unique_lock
  4. #include <condition_variable> // std::condition_variable
  5.  
  6. std::mutex mtx;
  7. std::condition_variable cv;
  8. bool ready = false;
  9.  
  10. void print_id (int id) {
  11. std::unique_lock<std::mutex> lck(mtx);
  12. while (!ready) cv.wait(lck);
  13. // ...
  14. std::cout << "thread " << id << '\n';
  15. }
  16.  
  17. void go() {
  18. std::unique_lock<std::mutex> lck(mtx);
  19. std::notify_all_at_thread_exit(cv,std::move(lck));
  20. ready = true;
  21. }
  22.  
  23. int main ()
  24. {
  25. std::thread threads[];
  26. // spawn 10 threads:
  27. for (int i=; i<; ++i)
  28. threads[i] = std::thread(print_id,i);
  29. std::cout << "10 threads ready to race...\n";
  30.  
  31. std::thread(go).detach(); // go!
  32.  
  33. for (auto& th : threads) th.join();
  34.  
  35. return ;
  36. }

  

condition_variable_any

与 std::condition_variable 类似,只不过 std::condition_variable_any 的 wait 函数可以接受任何 lockable 参数,而 std::condition_variable 只能接受 std::unique_lock<std::mutex> 类型的参数,除此以外,和 std::condition_variable 几乎完全一样。


std::atomic

原子操作是可以lock-free的算法和数据结构。

std::atomic_flag

  1. #include <iostream> // std::cout
  2. #include <atomic> // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
  3. #include <thread> // std::thread, std::this_thread::yield
  4. #include <vector> // std::vector
  5.  
  6. std::atomic<bool> ready(false); // can be checked without being set
  7. std::atomic_flag winner = ATOMIC_FLAG_INIT; // always set when checked
  8.  
  9. void count1m(int id)
  10. {
  11. while (!ready) {
  12. std::this_thread::yield();
  13. } // 等待主线程中设置 ready 为 true.
  14.  
  15. for (int i = ; i < ; ++i) {
  16. } // 计数.
  17.  
  18. // 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.
  19. // 此后其他线程执行 test_and_set 是 if 语句判断为 false,
  20. // 因此不会输出自身 ID.
  21. if (!winner.test_and_set()) {
  22. std::cout << "thread #" << id << " won!\n";
  23. }
  24. };
  25.  
  26. int main()
  27. {
  28. std::vector<std::thread> threads;
  29. std::cout << "spawning 10 threads that count to 1 million...\n";
  30. for (int i = ; i <= ; ++i)
  31. threads.push_back(std::thread(count1m, i));
  32. ready = true;
  33.  
  34. for (auto & th:threads)
  35. th.join();
  36.  
  37. return ;
  38. }

std::atomic_flag 的 test_and_set 函数是原子的:

test_and_set() 函数检查 std::atomic_flag 标志,如果 std::atomic_flag 之前没有被设置过,则设置 std::atomic_flag 的标志,并返回先前该 std::atomic_flag 对象是否被设置过,如果之前 std::atomic_flag 对象已被设置,则返回 true,否则返回 false。

std::atomic_flag 的 clear 函数,清除 std::atomic_flag 标志使得下一次调用 std::atomic_flag::test_and_set 返回 false。

参考文档:

https://zh.cppreference.com/w/cpp/thread

https://www.cnblogs.com/haippy/p/3284540.html

C++11 并发编程库的更多相关文章

  1. java5引入的并发编程库

    java5之后引入了java.util.concurrent包,专门用于解决java多线程问题.   AtomicInteger用于解决原子性操作(i++,i--的问题): AtomicInteger ...

  2. C++11 并发编程基础(一):并发、并行与C++多线程

    正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C ...

  3. C++11并发编程:多线程std::thread

    一:概述 C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植,对应多线程代码也必须要修改.现在在C++11中只需使用语 ...

  4. C++11并发编程:原子操作atomic

    一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...

  5. C++11并发编程:async,future,packaged_task,promise

    一:async std::async:用于创建异步任务,可以代替创建线程,函数原型:async(std::launch::async | std::launch::deferred, f, args. ...

  6. C++11并发编程1------并发介绍

    也许有人会觉得多线程和并发难用,复杂,还会让代码出现各种各样的问题,不过,其实它是一个强有力的工具,能让程序充分利用硬件资源,让程序运行得更快. 何谓并发: 两个或更多独立得活动同时发生.计算机中就是 ...

  7. C++11并发编程实战 免费书籍

    C++11 博客http://www.cnblogs.com/haippy/p/3284540.html 网上推荐的C++多线程基本都是C++ Concurrency in Action 英文版的,中 ...

  8. 11 并发编程-(线程)-信号量&Event&定时器

    1.信号量(本质也是一把锁)Semaphore模块 信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行, 信号量同一时间可以有5个任务拿到锁去执行, 如果说互斥锁是合租 ...

  9. Windows:C++11并发编程-条件变量(condition_variable)详解

    <condition_variable >头文件主要包含了与条件变量相关的类和函数.相关的类包括 std::condition_variable和 std::condition_varia ...

随机推荐

  1. SpringMVC+Mybatis学习

    简单Web项目搭建: 一.流程 1. 导包 n个springMVC: 2个mybatis<其中一个是mybatis-spring>: 3个jackson包: 2. xml配置 web.xm ...

  2. Windows UI自动化测试的XPATH实现 - WPATH

    https://segmentfault.com/a/1190000010339021 从事Windows 桌面应用自动化测试也有一些年了,现在谈这个话题并不流行.因为除了企业级应用,很少有公司会只选 ...

  3. Linux/Windows 配置config 使用ssh连接

    Linux 产看本地是否有ssh 公私钥 1 cd ~/.ssh 2 ls -a 有的话继续(没有 ssh-keygen 生成) 将公钥内容复制到要连接的服务器用户下 方法一 ssh-copy-id ...

  4. Numpy | 14 字符串函数

    本章函数用于对 dtype 为 numpy.string_ 或 numpy.unicode_ 的数组执行向量化字符串操作. 它们基于 Python 内置库中的标准字符串函数. 这些函数在字符数组类(n ...

  5. 8259A的初始化(单片)

    1.单片8259A的初始化流程图: 在单片的初始化中不需要ICW3,因为ICW3是指明主片和从片的连接情况的. 2.程序解析: (1)ICW1 MOV AL,13H (2)ICW2 MOV AL,08 ...

  6. Centrifugo  语言无关的实时消息服务

    Centrifugo 语言无关的实时消息服务,基于golang编写,提供了websocket 以及sockjs 的兼容处理,使用上很简单 同时也支持基于redis的扩展,以下是一个简单的运行测试 环境 ...

  7. 17-网页,网站,微信公众号基础入门(使用Adobe Dreamweaver CS6 制作网页/网站)

    https://www.cnblogs.com/yangfengwu/p/11351182.html https://www.cnblogs.com/yangfengwu/p/11200767.htm ...

  8. [RN] React Native 使用 阿里巴巴 矢量图标库 iconfont

    React Native 使用 阿里巴巴 矢量图标库 iconfont 文接上文: React Native 使用精美图标库react-native-vector-icons 本文主要讲述 如何 使用 ...

  9. Ubuntu下python3安装tkinter包

    case1: 首先sudo apt-get update(如果不更新很有可能找不到tkinter),然后sudo apt-get install python3-tk,安装完成后就可以使用了. cas ...

  10. cogs 997. [東方S2] 射命丸文

    二次联通门 : cogs 997. [東方S2] 射命丸文 /* cogs 997. [東方S2] 射命丸文 二维前缀和 枚举每个子矩阵 更新最大值.. 莫名rank1 */ #include < ...