C++多线程:thread
进程与线程
进程:系统资源分配的最小单元,通常被定义为一个正在运行的程序实例
线程:系统任务调度的最小单元
进程间通信:管道,信号量,信号,消息队列,共享内存,套接字
线程间通信:锁机制,信号量机制,信号机制,屏障
同步:保证任务片段的先后顺序
互斥:为了保证资源在同一时刻只能被一个线程使用,即数据一致性
同步方式
- 原子操作
- 信号量(semaphore)
- 读写信号量(rw_semaphore)
- 互斥锁
- 自旋锁(spinlock)
- 读写锁(rwlock)
- 顺序锁(seqlock)
有序资源分配法
银行家算法,死锁
生产者消费者问题
读者写者问题
哲学家就餐问题,互斥
C++多线程编程涉及的头文件如下
<thread>
<mutex>
<condition_variable>
<future>
<promise>
<packaged_task>
<atomic>
<async>
死锁
产生死锁的四个必要条件
条件 | 说明 |
---|---|
互斥 | 资源同一时刻只能被一个进程使用 |
请求并保持 | 进程在请资源时,不释放自己已经占有的资源 |
不剥夺 | 进程已经获得的资源,在进程使用完前,不能强制剥夺 |
循环等待 | 进程间形成环状的资源循环等待关系 |
死锁预防
破坏死锁产生的四个条件
- 打破互斥条件
- 打破不剥夺条件
- 打破请求并保持条件
- 打破循环等待条件
死锁避免
对分配资源做安全性检查,确保不会产生循环等待,如银行家算法
死锁检测
允许死锁的发生,但提供检测方法
死锁解除
已经产生死锁,强制剥夺资源或撤销线程
C++11线程库
C++11中定义了标准的线程库<thread>
,位于std
命名空间,编译时添加参数-std=c++11
构造函数
- 默认构造函数
thread()noexcept;
创建一个空的thread对象,该对象是不可joinable的 - 初始化构造函数
创建一个可template<typename Fn, typename... Args> explicit thread(Fn&& fn, Args&&... args);
joinable
的thread
对象
新产生的线程会调用fn()
,实参由args
给出
拷贝构造函数
thread(const thread&)=delete;
被禁用,thread对象不可拷贝
移动构造函数
thread(thread&& x) noexcept;
将一个临时(匿名的)thread对象赋值给另一个thread对象,调用成功后,x
将不代表任何thread对象
注意,每个thread对象在销毁(析构)前,要么调用join()
让主线程等待子线程执行完成
要么调用detach()
将子线程和主线程分离,两者必选其一,否则程序可能存在以下两个问题:
- 线程占用的资源将无法全部释放,造成内存泄漏
- 当主线程执行完成而子线程未执行完时,程序执行将引发异常
示例如下:
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
void f1(int n) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << n << " executing\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void f2(int& n) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
int n = 0;
std::thread t1; // t1 is not a thread
std::thread t2(f1, n+1); // pass by value
std::thread t3(f2, std::ref(n)); // pass by reference
std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
t2.join();
t4.join();
std::cout << "Final value of n is " << n << '\n';
}
赋值操作
- 拷贝赋值
被禁用,thread对象不可拷贝thread& operator=(const thread&)=delete;
- 移动赋值
若被赋值对象是非thread& operator=(thread&& rhs)noexcept;
joinable
的,则需要传递一个右值引用rhs
,否则terminate()
报错
示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <chrono> // std::chrono::seconds
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::sleep_for
void thread_task(int n) {
std::this_thread::sleep_for(std::chrono::seconds(n));
std::cout << "hello thread "
<< std::this_thread::get_id()
<< " paused " << n << " seconds." << std::endl;
}
int main(int argc, char *argv[]) {
std::thread threads[5];
std::cout << "Spawning 5 threads...\n";
for (int i = 0; i < 5; i++) {
threads[i] = std::thread(thread_task, i+1);
}
std::cout << "Done spawning threads! Now wait for them to join\n";
for (auto& t: threads) {
t.join();
}
std::cout << "All threads joined.\n";
return EXIT_SUCCESS;
}
其他成员函数
函数 | 说明 |
---|---|
joinable() |
判断线程对象是否支持调用join函数 |
join() |
阻塞线程对象所在的当前线程,直到线程对象执行完毕 |
detach() |
将线程对象从该对象所在线程分离,使其彼此独立执行 |
swap() |
交换两个线程对象所代表的底层句柄underlying handlers |
native_handle() |
返回与std::thread 的具体实现相关的线程句柄 |
hardware_concurrency()[static] |
检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint) |
注意
- 若某个线程已经执行完任务,但没有被join的话,该线程依然会被认为是一个活动的执行线程,因此也是可被join的
- 调用
detach()
后*this
不再代表任何线程实例joinable()==false
std::this_thread::get_id()==std::thread::id()
std::this_thread 命名空间中相关辅助函数
<thread>
头文件中不仅定义了thread
类,还提供了一个名为this_thread
的命名空间,此空间中包含一些功能实用的函数
函数 | 说明 |
---|---|
get_id() |
获取当前线程对象id |
yield() |
阻塞当前线程,操作系统调度执行另一线程 |
sleep_until() |
阻塞当前线程,直到某个时间点 |
sleep_for() |
阻塞线程休眠指定的时间片time span,由于线程调度等原因,实际休眠时间可能比sleep_duration 更长 |
示例如下:
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
void f1(int n) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << n << " executing\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void f2(int& n) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread 2 executing\n";
++n;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
int n = 0;
std::thread t1; // t1 is not a thread
std::thread t2(f1, n+1); // pass by value
std::thread t3(f2, std::ref(n)); // pass by reference
std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
// 阻塞主线程,等待t2执行完毕
t2.join();
// 阻塞主线程,等待t4执行完毕
t4.join();
std::cout << "Final value of n is " << n << '\n';
}