线程(thread):
在 C++ 语言里,线程就是一个能够独立运行的函数。比如你写一个 lambda 表达式,就可以让它在线程里跑起来:
auto f = []() // 定义一个lambda表达式
{
cout << "tid=" <<
this_thread::get_id() << endl;
};
thread t(f); // 启动一个线程,运行函数f
任何程序一开始就有一个主线程,它从 main() 开始运行。主线程可以调用接口函数,创建出子线程。子线程会立即脱离主线程的控制流程,单独运行,但共享主线程的数据。程序创建出多个子线程,执行多个不同的函数,也就成了多线程。多线程的好处,比如任务并行、避免 I/O 阻塞、充分利用 CPU、提高用户界面响应速度,等等。坏处,比如同步、死锁、数据竞争、系统调度开销等。
所以在 C++ 多线程编程里读取 const 变量总是安全的,对类调用 const 成员函数、对容器调用只读算法也总是线程安全的。“读而不写”就不会有数据竞争。
在 C++ 里,有四个基本的工具:仅调用一次、线程局部存储、原子变量和线程对象。
多线程开发实践:
仅调用一次
程序免不了要初始化数据,这在多线程里却是一个不大不小的麻烦。因为线程并发,如果没有某种同步手段来控制,会导致初始化函数多次运行。为此,C++ 提供了“仅调用一次”的功能,可以很轻松地解决这个问题。这个功能用起来很简单,你要先声明一个 once_flag 类型的变量,最好是静态、全局的(线程可见),作为初始化的标志:
static std::once_flag flag; // 全局的初始化标志
然后调用专门的 call_once() 函数,以函数式编程的方式,传递这个标志和初始化函数。这样 C++ 就会保证,即使多个线程重入 call_once(),也只能有一个线程会成功运行初始化。下面是一个简单的示例,使用了 lambda 表达式来模拟实际的线程函数。你可以把GitHub 仓库里的代码下到本地,实际编译运行看看效果:
https://github.com/chronolaw/cpp_study/blob/master/section3/thread.cpp
auto f = []() // 在线程里运行的lambda表达式
{
std::call_once(flag, // 仅一次调用,注意要传flag
[](){ // 匿名lambda,初始化函数,只会执