1、C++线程基础
在C++11之前,C++语言没有引入线程的概念,但是可以借助操作系统平台提供的API,比如Linux的<pthread.h>或者Windows下的<windows.h> 来实现多线程。
在C++11之后,C++提供了语言层面上的多线程支持,头文件thread.h。解决了跨平台(Linux、Windows)的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等。
thread.h和pthread.h的区别:
(1)pthread.h是POSIX线程标准的头文件,而thread.h是C++11标准中的线程库头文件。
(2)pthread.h适用于C语言和C++语言,而thread.h只适用于C++语言。
(3)pthread.h中使用的函数和类型更加底层,需要手动管理线程的创建、销毁、同步等操作,而thread.h中的线程库提供了更高层次的抽象,使得线程编程更加方便和安全。
(4)pthread.h中的线程库具有更好的移植性,可以在各种Unix系统上使用,而thread.h的支持范围相对较小。
(5)如果你是在C++11标准下进行线程编程,建议使用thread.h。如果你需要更底层的控制或者需要在多种Unix系统上移植代码,可以选择使用pthread.h。
2、thread类
2.1、创建线程
创建线程举例:
创建线程:需要把【线程处理函数】添加到【线程】当中。
形式1:
std::thread myThread(thread_function);
myThread.join();
// 线程处理函数形式为:void thread_function()
// 同一个函数可以代码复用,创建多个线程
形式2:
std::thread myThread(thread_function(100));
myThread.join();
//函数形式为:void thread_function(int x)
//同一个函数可以代码复用,创建多个线程
形式3:
std::thread(thread_function, 1).detach();
// 直接创建线程,没有名字
// 函数形式为void thread_function(int x)
代码:
#include <iostream>
#include <thread>
using namespace std;
void thread_function_1()
{
cout << "thread_function_1!" << endl;
}
void thread_function_2(int x)
{
cout << "thread_function_2! " << x << endl;
}
int main ()
{
std::thread thread_1(thread_function_1);
thread_1.join();
std::thread thread_2(thread_function_2, 100);
thread_2.join();
cout << "main!" << endl;
return 0;
}
结果:
thread_function_1!
thread_function_2! 100
main!
线程构造函数详细介绍:
std::thread 在 <thread> 头文件中声明,因此使用 std::thread 时需要包含 <thread> 头文件。其中std::thread 构造方法如下:
默认构造函数 | thread() noexcept; |
初始化构造函数 | template <class Fn, class... Args> |
拷贝构造函数 | thread (const thread&) = delete; |
move构造函数 | thread (thread&& x) noexcept; |
(1)默认构造函数:创建一个空thread对象,该对象非joinable。
(2)初始化构造函数:创建一个thread对象,该对象会调用Fn函数,Fn函数的参数由args指定,该对象是joinable的。
(3)拷贝构造函数:被禁用,意味着thread对象不可拷贝构造。
(4)move构造函数:移动构造,执行成功之后x失效,即x的执行信息被移动到新产生的thread对象,该对象非joinable。
代码:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void thread_function_2(int n)
{
for (int i = 0; i < 5; ++i) {
cout << "thread_function_2: i = " << i << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void thread_function_3(int& n)
{
for (int i = 0; i < 5; ++i) {
cout << "thread_function_3: i = " << i << endl;
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main()
{
int num = 0;
std::thread thread_1; // thread_1 is not a thread
std::thread thread_2(thread_function_2, num + 1); // pass by value
std::thread thread_3(thread_function_3, std::ref(num)); // pass by reference
std::thread thread_4(std::move(thread_3)); // thread_4 is now running thread_function_3(). thread_3 is no longer a thread
//thread_1.join(); 崩溃
thread_2.join();
//thread_3.join(); 崩溃; 因为thread_3已经move到thread_4
thread_4.join();
cout << "Final value of num is " << num << endl;
}
结果:
thread_function_2: i = thread_function_3: i = 00
thread_function_2: i = 1
thread_function_3: i = 1
thread_function_2: i = 2
thread_function_3: i = 2
thread_function_3: i = thread_function_2: i = 33
thread_function_2: i = 4
thread_function_3: i = 4
Final value of num is 5
一个thread对象是否是joinable的判断方式:
(1) 如果一个线程正在执行,那么它是jionable的。
(2)下列任一情况,都是非joinable:
(1)默认构造器构造的。
(2)通过移动构造获得的。
(3)调用了join或者detach方法的。
当线程启动后,一定要确定以什么方式结束:
(1)detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
(2)join方式,等待启动的线程完成,才会继续往下执行。
2.2、join()函数
调用join()函数后,当前线程阻塞等待子线程结束后继续执行。
代码:
#include <iostream>
#include <thread>
using namespace std;
void thread_function_1()
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_1! " << i << endl;
}
}
void thread_function_2(int x)
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_2! " << i << " " << x << endl;
}
}
int main ()
{
std::thread thread_1(thread_function_1);
thread_1.join();
cout << "thread_1 over!" << endl;
std::thread thread_2(thread_function_2, 100);
thread_2.join();
cout << "thread_2 over!" << endl;
cout << "main!" << endl;
return 0;
}
结果:
thread_function_1! 0
thread_function_1! 1
thread_function_1! 2
thread_function_1! 3
thread_function_1! 4
thread_function_1! 5
thread_function_1! 6
thread_function_1! 7
thread_function_1! 8
thread_function_1! 9
thread_1 over!
thread_function_2! 0 100
thread_function_2! 1 100
thread_function_2! 2 100
thread_function_2! 3 100
thread_function_2! 4 100
thread_function_2! 5 100
thread_function_2! 6 100
thread_function_2! 7 100
thread_function_2! 8 100
thread_function_2! 9 100
thread_2 over!
main!
2.3、detach()函数
调用detach()函数后,当前线程和子线程分离,不必等待子线程结束,即子线程变成守护线程。
代码:
#include <iostream>
#include <thread>
using namespace std;
void thread_function_1()
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_1! " << i << endl;
}
}
void thread_function_2(int x)
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_2! " << i << " " << x << endl;
}
}
int main ()
{
std::thread thread_1(thread_function_1);
std::thread thread_2(thread_function_2, 100);
thread_1.detach();
thread_2.detach();
cout << "main!" << endl;
return 0;
}
结果:
main!thread_function_1! thread_function_2! 00 100
thread_function_1!
thread_function_2! 1 100
thread_function_2! 21
100
thread_function_2! 3 100
注意:
(1)detach()函数的作用是将线程与其父线程分离,使得子线程可以在后台继续执行而不受父线程的控制。具体来说,当调用detach()函数时,父线程不再等待子线程执行完成,而是立即返回到主线程继续执行后续的代码。这意味着子线程将在后台独立运行,直到其执行完成或终止。
(2)调用detach()函数以后,可以理解为被分离的线程就完全独立了。但是根据《unix编程手册》的说法,主线程退出之后,这个进程中的所有线程都会被退出。本文觉得,在UNIX中主线程退出后,子线程也随之退出。
2.4、this_thread【多线程之命名空间】
在 C++11 中不仅添加了线程类,还添加了一个关于线程的命名空间 std::this_thread,在这个命名空间中提供了四个公共的成员函数,通过这些成员函数就可以对当前线程进行相关的操作了
2.4.1、get_id()函数
调用命名空间 std::this_thread 中的 get_id() 方法可以得到当前线程的线程 ID。
// 函数原型如下:
thread::id get_id() noexcept;
代码:
#include <iostream>
#include <thread>
using namespace std;
void thread_function_1()
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_1! " << i << " " << this_thread::get_id() << endl;
}
}
void thread_function_2(int x)
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_2! " << i << " " << x << " " << this_thread::get_id() << endl;
}
}
int main ()
{
std::thread thread_1(thread_function_1);
thread_1.join();
cout << "thread_1 over!" << endl;
std::thread thread_2(thread_function_2, 100);
thread_2.join();
cout << "thread_2 over!" << endl;
cout << "main!" << endl;
return 0;
}
结果:
thread_function_1! 0 140719225247488
thread_function_1! 1 140719225247488
thread_function_1! 2 140719225247488
thread_function_1! 3 140719225247488
thread_function_1! 4 140719225247488
thread_function_1! 5 140719225247488
thread_function_1! 6 140719225247488
thread_function_1! 7 140719225247488
thread_function_1! 8 140719225247488
thread_function_1! 9 140719225247488
thread_1 over!
thread_function_2! 0 100 140719225247488
thread_function_2! 1 100 140719225247488
thread_function_2! 2 100 140719225247488
thread_function_2! 3 100 140719225247488
thread_function_2! 4 100 140719225247488
thread_function_2! 5 100 140719225247488
thread_function_2! 6 100 140719225247488
thread_function_2! 7 100 140719225247488
thread_function_2! 8 100 140719225247488
thread_function_2! 9 100 140719225247488
thread_2 over!
main!
2.4.2、sleep_for()函数
由于进程被创建后就有5种状态,所以同样的线程被创建后也有这五种状态:创建态,就绪态,运行态,阻塞态(挂起态),退出态(终止态)。
线程和进程的执行有很多相似之处,在计算机中启动的多个线程都需要占用 CPU 资源,但是 CPU 的个数是有限的并且每个 CPU 在同一时间点不能同时处理多个任务。为了能够实现并发处理,多个线程都是分时复用CPU时间片,快速的交替处理各个线程中的任务。 因此多个线程之间需要争抢CPU时间片,抢到了就执行,抢不到则无法执行(因为默认所有的线程优先级都相同,内核也会从中调度,不会出现某个线程永远抢不到 CPU 时间片的情况)。
命名空间 this_thread 中提供了一个休眠函数 sleep_for(),调用这个函数的线程会马上从运行态变成阻塞态并在这种状态下休眠一定的时长,因为阻塞态的线程已经让出了 CPU 资源,代码也不会被执行,所以线程休眠过程中对 CPU 来说没有任何负担。
// 函数原型如下,参数需要指定一个休眠时长,是一个时间段
template <class Rep, class Period>
void sleep_for (const chrono::duration<Rep,Period>& rel_time);
代码:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void thread_function_1()
{
for (int i = 0; i < 10; ++i) {
cout << "thread_function_1! " << i << " " << this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // sleep 10ms
}
}