计算机 · 2021年5月22日 0

深入应用C++11笔记 第5章

使用C++11让多线程开发变得简单

线程

  • 可以通过std::thread创建一个线程
  • 要让线程函数的生命周期在线程对象的生命周期之内,否则会出错
  • 线程对象需要join或detach
  • 线程不可以复制但是可以移动(std::move)
  • 几个线程相关的api:
    • get_id()获取当前线程id
    • std::thread::hardware_concurrency()获取CPU核心数量
    • std::this_thread::sleep_for让当前线程休眠

互斥量

其实没啥讲的,就是4种语义的互斥量:

  • std::mutex
  • std::timed_mutex
  • std::recursive_mutex
  • std::recursive_timed_mutex

调用时除了lock,还可以用try_lock非阻塞式调用。

条件变量

可以使用condition_varialbecondition_variable_any来实现同步队列。示例代码参考cppreference网站的示例

条件变量的语义很好理解,故名思义,只是需要注意两点,一是需要注意循环检查predicate是否为true,condition_variable被唤醒的时候是不保证predicate为true的;二是有一个疑问为什么条件变量非得要和mutex配合使用,答案是为了保护predicate的原子性,可以阅读一下这个答案下的评论:

the mutex is not to protect the condition variable; it is to protect the predicate data, but I think you know that from reading your comment that followed that statement. You can signal a condition variable legally, and fully supported by implementations, post-unlock of the mutex wrapping the predicate, and in fact you’ll will relieve contention in doing so in some cases.

原子变量

std::atomic<T>,提供以下api:

  • operator=
  • is_lock_free
  • store
  • load
  • operator T
  • exchange
  • compare_exchange_weak
  • compare_exchange_strong

对于int这些常见基础类型的原子变量版本,还有相应的特化函数模板实现,具体查cppreference就可以了。

call_once与once_flag

为了保证多线程下某个函数只被调用一次,可以使用call_once来包裹该函数,同时需要声明一个once_flag变量。

异步操作类

  • std::future
    future不支持拷贝,如果需要放进容器里需要使用shared_future
    future的api:getvalidwaitwait_forwait_until
  • std::promise
  • std::package_task

直接理解这三个概念似乎有点困难,所以可以先看下cppreference给的例子:

   #include <iostream>
   #include <future>
   #include <thread>

   int main()
   {
       // future from a packaged_task
       std::packaged_task<int()> task([]{ return 7; }); // wrap the function
       std::future<int> f1 = task.get_future();  // get a future
       std::thread t(std::move(task)); // launch on a thread

       // future from an async()
       std::future<int> f2 = std::async(std::launch::async, []{ return 8; });

       // future from a promise
       std::promise<int> p;
       std::future<int> f3 = p.get_future();
       std::thread( [&p]{ p.set_value_at_thread_exit(9); }).detach();

       std::cout << "Waiting..." << std::flush;
       f1.wait();
       f2.wait();
       f3.wait();
       std::cout << "Done!\nResults are: "
                 << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
       t.join();
   }

可以看出,future是用来保存函数的返回值的,package_taskasync函数都可以返回futurepromise则是类似于多个线程共享的变量,其他线程通过修改这个共享的变量来返回计算结果,而在promise内部还是用了future来保存这个计算结果;package_task则是可调用对象的包装类,返回一个future,但package_task自身不会创建线程,需要借助thread类来运行;async则是可以创建线程运行函数,并且返回future

线程异步操作函数async

函数原型async(std::launch::async|std::launch::deferred, f, args...),使用async时会立即创建线程,使用deferred的时候调用future的get/wait方法才创建线程;