在C++多线程专栏中,我讲解了std::async,std::future,std::promise以及内存序相关概念,基本上涵盖了C++事件处理相关的基本构建块。
为了保证对C++多线程中相关线程概念的全部介绍,后期会增加相应的std::thread, std::mutex, std::lock_guard, std::unique_lock等相关介绍。
而本文便是第一部分,std::thread的相关介绍和使用讲解。
一个std::thread代表一个可执行单元。简单来说
下面是四种创建线程的方式
- #include
- #include
-
- class A {
- public:
- void test_thread() {
- std::cout << "A member function thread\n";
- }
- };
-
- void test_thread() {
- std::cout << "function thread\n";
- }
-
- class background_task {
- public:
- void operator()() const {
- std::cout << "functor thread\n";
- }
- };
-
- int main() {
- // 1.通过类的成员函数创建线程
- A a;
- std::thread t1(&A::test_thread, a);
- // 2. 通过正常函数创建线程
- std::thread t2(test_thread);
- // 3. 通过函数对象创建线程
- std::thread t3(background_task());
- // 4. 通过lambda表达式创建线程
- std::thread t4([]() { std::cout << "lambda thread\n";});
- t1.join();
- t2.join();
- t3.join();
- t4.join();
- return 0;
- }
若你构建上述程序会出现如下错误
qls@qls-VirtualBox:~/cpp_learn$ g++ -o main -c a.cc -std=c++17 -lpthread
a.cc: In member function ‘void background_task::operator()() const’:
a.cc:19:3: error: expected ‘;’ before ‘}’ token
19 | }
| ^
a.cc: In function ‘int main()’:
a.cc:29:17: warning: parentheses were disambiguated as a function declaration [-Wvexing-parse]
29 | std::thread t3(background_task());
| ^~~~~~~~~~~~~~~~~~~
a.cc:29:17: note: replace parentheses with braces to declare a variable
29 | std::thread t3(background_task());
| ^~~~~~~~~~~~~~~~~~~
| -
| { -
| }
a.cc:34:6: error: request for member ‘join’ in ‘t3’, which is of non-class type ‘std::thread(background_task (*)())’
34 | t3.join();
| ^~~~
上述便是C++中最C++’s most vexing parse相关错误,也即将声明都解释成函数声明。因此会报上述错误。
解决方案便为 将t3改成如下方式
- // 3. 通过函数对象创建线程
- std::thread t3{background_task()};
一个std::thread生命周期随着其可调用单元的结束而终结。为了控制生命周期,你可以选择
- join() // 阻塞直到子线程退出
-
- detach() // 分离子线程
join()的使用场景如下:
detach()使用场景如下:
判断一个std::thread是否可join或者detach,则需要了解joinable相关的概念。
joinable:
使用join和detach会碰到如下两个异常
当一个可joinable线程即没调用join也没调用detach时,其析构函数被调用的时候,C++会调用std::terminate
当一个可joinable线程调用起join或者detach接口超过一次,则会抛出std::system_error异常
关于上述代码示例,本文就不再给出,感兴趣可以自己实验。
std::thread本质上是一个可变模版。因此可以向其传递任何数量的参数(可参考std::thread - cppreference.com
其构造函数如下形式
- template< class Function, class... Args >
- explicit thread( Function&& f, Args&&... args );
NOTE:
默认场景下,std::thread的参数是拷贝到其内部存储中,在那里他们会被可执行线程访问,然后将其作为右值(像临时对象)传递到可执行单元。
下面这个例子来自《C++ Concurrency in Action 2th》。
- void f(int i,std::string const& s);
- std::thread t(f,3,”hello”);
上述新创建的线程t的内部存储的是const char*变量,在可执行线程内部将该变量转换为std::string,并传递给可调用单元f。
由于std::thread仅仅通过拷贝相应的参数,因此在使用std::thread传递参数时,需要额外注意相应参数生命周期的问题,防止服务出现bug。
譬如《C++ Concurrency in Action 2th》给出如下几种场景
如下程序所示(我进行了相应的补全)
- #include
- #include
-
- void f(int i,std::string const& s) {
- std::cout << s << i << "\n";
- }
-
- void oops(int some_param) {
- char buffer[1024];
- sprintf(buffer, "%i",some_param);
- std::thread t(f,3,buffer);
- t.detach();
- }
-
- int main() {
- oops(10);
- return 0;
- }
同std::unique_ptr类似,std::thread拥有对可执行线程的所有权。
转换std::thread的可执行线程所有权至少有如下两种使用场景
下面这段代码来自《C++ Concurrency In Action》2th。
- void some_function();
- void some_other_function();
- std::thread t1(some_function);
- std::thread t2=std::move(t1);
- t1=std::thread(some_other_function);
- std::thread t3;
- t3=std::move(t2);
- t1=std::move(t3);
上述主要工作如下
此处接口来自std::thread – cppreference.com
- t.join() // 等待线程t的可执行单元的结束
-
- t.detach() // 分离线程t,使其作为后台线程运行
-
- t.joinable() // 如果线程t是可joinable,该接口返回true
-
- t.get_id()
-
- std::this_thread::get_id() // 这两个接口返回线程标识
-
- std::thread::hardware_concurrency() // 表示一个机器最多能启动多少线程
-
- std::this_thread::sleep_until(absTime) // 让线程 t 进入休眠状态,直到时间点 absTime。
-
- std::this_thread::sleep_for(relTime) // 使线程 t 在relTime 时间间隔内休眠。
-
- std::this_thread::yield() // 使系统去调度另一个线程去执行
-
- t.swap(t2)
-
- std::swap(t1, t2) // 这两个接口交换std::thread底层的可执行线程
通过本文,可以初步了解并使用std::thread相关工具类。