c++(2·多线程)
多线程
基础概念
- 进程:一个正在运行的应用程序被称为一个进程
- 线程:是进程中的实际运行单位,每个进程可以拥有至少一个的线程
- 同步:一个任务完成后另一个任务才能开始
- 异步:一个任务的开始和完成不会直接影响另一个任务的开始和完成
- 并发:有多个任务在单核CPU被处理,任务被交替执行
- 并行:有多个任务在多核CPU被处理,任务可以被同时执行
多线程
多线程是并发/并行的技术实现,使得程序能够在同一时间执行多于一个线程,进而提升整体处理性能,但是其实它并非一定会提升性能
- 单核处理CPU任务:多线程会时间切片,交替执行任务,这是同步且并发的,CPU任务不能被异步处理,且任务调度需要时间,因此不会提高效率
- 单核处理CPU + GPU任务:由于GPU是高度并行的,因此可以在执行CPU任务时同时处理多个GPU任务,CPU和GPU两者之间是异步且并行的,会提高效率
- 多核处理CPU任务:多个任务被异步处理,这是异步且并行的,会提高效率
std::thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <thread>
thread a(
[]{
cout << "Hello, " << flush;
}
)//创建线程a并执行函数
a.join();//线程结束后清理
void printId(int id) {
std::cout << id << std::endl;
}
thread th(print, 1);//有参线程
th.join();
展开
- thread()创建一个线程
- thread(Fn&& fn, Args&&… args)创建线程并执行函数(可调用对象),以args为参数
- thread(thread&& x) 移动构造(注意没有拷贝构造,否则会有严重的混乱问题)
- join()阻塞主线程后续的指令,线程结束清理资源,它能确保线程执行的完整性
- detach()将线程与调用其的线程分离,彼此独立执行,会自动清理资源(此函数必须在线程创建时立即调用,且调用此函数会使其不能被join)
- 注:如果没有调用join 或 detach,程序的行为是未定义的,可能会内存泄漏等问题
- joinable()返回线程是否可以执行join函数
引用args
1
2
3
4
5
6
7
8
template<typename T>
void changevalue(T &x, T val) {
x = val;
}
int x = 0;
thread th(changevalue, x, 5);//❌:不能将左值传给右值
thread th(changevalue, ref(x), 5);//✅
th.join();
展开
如果bind关联的函数的参数是有引用的,我们应该在thread创建时以引用形式传递变量
但thread 参数是万能引用,会发生拷贝,利用std::ref和std::cref可以以引用的方式传递,防止拷贝
std::atomic和std::mutex
多个线程由于并行异步的关系,线程间无序执行,操作共享变量时会出错,会发生数据竞争:指多个线程同时访问同一个共享变量,并且至少有一个访问是写操作,其他为读操作
例如一个线程正在读取变量的值,而另一个线程同时在修改这个值,那么读取线程可能会得到一个不一致或错误的值,
为了应对这种情况,使用:
1
2
3
4
mutex mtx;
mtx.lock();
mtx.unlock();
atomic_int n = 0;
展开
- mutex互斥量:通过mutex将共享变量定义为互斥量,当一个线程将mutex lock()锁住时,其它的线程就不能操作mutex,其他线程会被阻塞,直到这个线程将mutex unlock()解锁,可以用try_lock()查看mutex是否被锁住,mutex在每次被修改都要上锁、解锁,因此效率很低
- atomic原子:通过atomic_数据类型 定义一个原子,表明它不能被并行执行,需要被同步操作,因此免去了mutex每次上锁、解锁的时间消耗
std::async
- async (Fn&& fn, Args&&… args)开始执行fn函数,以args为参数,返回std::future
- async (launch policy, Fn&& fn, Args&&… args);其中launch是枚举,表示启动策略
- launch::async异步启动
- launch::deferred同步启动
async vs. thread
- async是函数而非类,返回结果为std::future未来
- async可以选择启动策略
- async会自动管理线程的生命周期,也就是不需要join() / detach()释放资源
- thread适用于对线程进行精细控制的场景,async适用于简单的线程任务
std::future
1
2
3
4
5
6
template<class ... Args>
decltype(auto) sum(Args&&... args) {
return (0 + ... + args);
}
future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
cout << val.get() << endl;
展开
- 创建一个future<对应函数返回类型>类型的变量等于async函数的结果,还可以用来检测线程是否已结束对应函数返回类型>
- get()等待线程结束并获取返回值
- 检查线程是否结束wait_for()线程结束则返回future_status::ready,若没结束则返回future_status::timeout
std::promise
thread通过引用获取返回值
由于thread类并没有提供直接获取返回值的函数,想要获取thread线程的返回值,可以通过引用传递,参数为引用会直接修改被绑定的那些参数
thread通过promise获取返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class ... Args> decltype(auto) sum(Args&&... args) {
return (0 + ... + args);
}
template<class ... Args> void sum_thread(promise<long long> &val, Args&&... args) {
val.set_value(sum(args...));//设置值为sum()
}
int main() {
promise<long long> sum_value;
thread get_sum(sum_thread<int, int, int>, ref(sum_value), 1, 10, 100);//将promise以引用的方式传入函数
cout << sum_value.get_future().get() << endl;//构造future对象,它的值和primise相同,get获取返回值
get_sum.join();
return 0;
}
展开
promise承诺 实际上是std::future的一个包装,作用是线程的通讯,例如在一个线程t1中设置值(任何类型),可供相绑定的std::future对象在另一线程t2中获取,
- promise()
- promise(allocator_arg_t aa, const Alloc& alloc)使用特定的内存分配器alloc构造对象
- promise (promise&& x) 移动构造
- get_future()构造一个future对象,其值与promise相同
- set_value (const T& val / T&& val)设置值
std::this_thread
this_thread是个命名空间
- get_id() 获取线程id
- sleep睡眠( const std::chrono::duration< Rep, Period>& sleep_duration )线程等待sleep_duration(5000表示5秒)
- yield()让步 暂时放弃线程的执行,将主动权 / 时间片 交给其他线程
注意:主线程指main线程
本文由作者按照 CC BY 4.0 进行授权