C++11引入了std::thread,用于多线程编程。
本文从实际应用的角度,分享一些常见的std::thread基础用法。
创建线程 我们从一个最简单的例子入手:
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <thread> void hello () { std ::cout << "Hello std::thread!" << std ::endl ; } int main () { std ::thread t (hello) ; t.join(); }
上面的例子中我们做了四件事:
引入了头文件#include <thread>
编写了一个独立的函数hello,它没有参数,没有返回值
创建了std::thread对象,它的参数是函数指针hello
调用了std::thread的join成员函数,join会等待线程直到执行完成,我们在稍后会介绍它
构造一个std::thread对象 从上面最简单的例子可以看出,我们通常这样构造一个线程对象
1 2 void do_some_work () ;std ::thread my_thread (do_some_work) ;
其实,可以使用任何可Callable
的类型,来构造std::thread,比如函数对象:
1 2 3 4 5 6 7 8 9 10 11 class background_task { public : void operator () () const { do_something(); do_something_else(); } }; background_task f; std ::thread my_thread (f) ;
使用Lambda 或者我们可以使用匿名函数,就像下面这样:
1 2 3 4 std ::thread my_thread ([]( do_something(); do_something_else(); });
join & detach 在上面最简单的例子中,我们知道了,使用join()可以等待线程直到执行完成。
1 2 3 4 5 6 7 std ::thread t1 ([]() { for (int i = 0 ; i < 10 ; i++) { std ::cout << "in t1: " << i + 1 << std ::endl ; } }) ;t1.join(); std ::cout << "t1 done." << std ::endl ;
它的输出是
1 2 3 4 5 6 7 8 9 10 11 in t1: 1 in t1: 2 in t1: 3 in t1: 4 in t1: 5 in t1: 6 in t1: 7 in t1: 8 in t1: 9 in t1: 10 t1 done.
直到t1线程执行完成后,才打印出t1 done
。
与join对应的,detach用来在后台运行线程。
我们尝试把上面例子的join改成detach
1 2 3 4 5 6 7 8 std ::thread t1 ([]() { for (int i = 0 ; i < 10 ; i++) { std ::cout << "in t1: " << i + 1 << std ::endl ; } }) ;t1.detach(); std ::cout << "t1 done." << std ::endl ;std ::this_thread::sleep_for(std ::chrono::seconds(1 ));
打印的结果变成了
1 2 3 4 5 6 7 8 9 10 11 t1 done. in t1: 1 in t1: 2 in t1: 3 in t1: 4 in t1: 5 in t1: 6 in t1: 7 in t1: 8 in t1: 9 in t1: 10
传参 在std::thread构造函数中添加额外的参数就好了。
1 2 3 4 5 void foo (int m, int n) { std ::cout << m << n << std ::endl ; } std ::thread t (foo, 5 , 4 ) ;
注意,给类的成员函数传参,要加上对象指针。就像下面这样:
1 2 3 4 5 6 7 8 struct Foo { void bar (int n) { std ::cout << n << std ::endl ; } }; Foo foo; std ::thread t (&Foo::bar, &foo, 5 ) ;t.join();
返回值 我们如何从线程中拿到返回值呢?
Lambda + 引用捕获 1 2 3 4 5 6 int r = 0 ;std ::thread t ([&r]() { r = 10 ; }) ;t.join(); std ::cout << r << std ::endl ;
使用std::promise和std::future std::promise和std::future提供了一种可以从异步方法中拿到执行结果的机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <thread> #include <future> void foo (int m, int n, std ::promise<int >&& p) { p.set_value(m * n); } int main () { std ::promise<int > p; auto f = p.get_future(); std ::thread t (&foo, 5 , 6 , std ::move(p)) ; t.join(); int i = f.get(); std ::cout << i << std ::endl ; }
使用std::async 使用std::async可以简化上面例子的写法,它异步的执行一个函数,返回std::future类型的结果。
上面的例子可以写成:
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> #include <thread> #include <future> int foo (int m, int n) { return m * n; } int main () { auto f = std ::async(foo, 5 , 6 ); std ::cout << f.get() << std ::endl ; }
在线程之间共享数据 假设我们用一个单例来管理这些共享数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Singleton {public : static Singleton& instance () { static Singleton s; return s; } void add (int n) { v_.emplace_back(n); } int count () const { return v_.size(); } private : Singleton() {} public : Singleton(Singleton const &) = delete ; void operator =(Singleton const &) = delete ; private : std ::vector <int > v_; }; int main () { std ::vector <std ::thread> threads; for (int i = 0 ; i < 10 ; ++i) { threads.emplace_back(std ::thread([](int i) { Singleton::instance().add(i); }, i)); } for (auto & t : threads) { t.join(); } std ::cout << Singleton::instance().count() << std ::endl ; }
我们同时run了10个线程,每个线程往池子里丢一个数据。理论上应该是10个,但实际上可能并非如此。
加锁 我们对上面的Singleton稍作修改,加一个互斥量,并对往池子丢数据的地方加锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Singleton {public : static Singleton& instance () { static Singleton s; return s; } void add (int n) { std ::lock_guard<std ::mutex> guard(mutex_); v_.emplace_back(n); } int count () const { return v_.size(); } private : Singleton() {} public : Singleton(Singleton const &) = delete ; void operator =(Singleton const &) = delete ; private : std ::mutex mutex_; std ::vector <int > v_; };
这样,我们就能得到期望的结果了。
终止线程 有可能我们在关闭主程序时,我们的子线程还没结束。我们需要正常的结束子线程。
加个标志 1 2 3 4 5 6 7 8 9 10 std ::atomic_bool stop_flag = false ;void foo () { while (true ) { if (stop_flag) { return ; } } }
然后在主线程中改变这个标志
1 2 3 4 std ::thread t (foo) ;std ::this_thread::sleep_for(std ::chrono::milliseconds(100 )); stop_flag = true ; t.join();
其他 同时跑几个线程合适? 推荐使用std::thread::hardware_concurrency()
, 在多核架构的运行环境上,这个返回值一般对应核的颗数。
获取当前线程的标识 使用std::this_thread::get_id()
std::async支持两种策略 默认使用std::launch::async , 惰性调用时使用std::launch::deferred
文章中没提到的 本文介绍的只是基础的多线程使用。有很多主题都没有介绍,比如如何在多个线程中进行同步,原子操作等等。
Reference