c++多线程编程
引用
C++多线程基础教程: https://www.cnblogs.com/zizbee/p/13520823.html
C++多线程(一)thread类: https://blog.csdn.net/coolwriter/article/details/79883253
C++线程同步的四种方式(Windows): https://blog.csdn.net/s_lisheng/article/details/74278765
死锁
1.互斥条件:一个资源每次只能被一个进程使用;
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
头文件
- <atomic>
- <thread>
- <mutex>
- <condition_variable>
- <future>
创建多线程
#include<thread>
using namespace std;
void show()
{
cout << "hello cplusplus!" << endl;
}
int main()
{
//栈上
thread t1(show); //根据函数初始化执行
thread t2(show, *args);
thread t3(show);
//线程数组
thread th[3]{thread(show), thread(show), thread(show)};
//堆上
thread *pt1(new thread(show));
thread *pt2(new thread(&show));
thread *pt3(new thread(show));
//线程指针数组
thread *pth(new thread[3]{thread(show), thread(show), thread(show)});
return 0;
}
join、detach
join 是让当前主线程等待所有的子线程执行完,才能退出。
cout << threads[i].joinable() << endl;//判断线程是否可以join
threads[i].join();//主线程等待当前线程执行完成再退出
线程 detach 脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。
线程 detach以后,子线程会成为孤儿线程,线程之间将无法通信。
thread th(show);
th.detach();//脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。
//detach以后,子线程会成为孤儿线程,线程之间将无法通信。
cout << th.joinable() << endl;
获取CPU核心个数
auto n = thread::hardware_concurrency();
atomic
原子操作,使用方法
// 这个a就不会因为线程访问导致数据错误了
std::atomic<int> a = 0;
mutex
锁。开关锁可以控制资源的访问
用法
std::mutex.lock();
std::mutex.unlock();
std::lock_guard<std::mutex> guard(obj); // 也可以用unique_lock
lock_guard | unique_lock | |
---|---|---|
手动lock与手动unlock | 不支持 | 支持 |
参数 | 支持adopt_lock | 支持adopt_lock/try_to_lock/defer_lock |
异步线程
async与future:
async是一个函数模板,用来启动一个异步任务,它返回一个future类模板对象,future对象起到了占位的作用,刚实例化的future是没有储存值的,但在调用future对象的get()成员函数时,主线程会被阻塞直到异步线程执行结束,并把返回结果传递给future,即通过FutureObject.get()获取函数返回值。
相当于你去办政府办业务(主线程),把资料交给了前台,前台安排了人员去给你办理(async创建子线程),前台给了你一个单据(future对象),说你的业务正在给你办(子线程正在运行),等段时间你再过来凭这个单据取结果。过了段时间,你去前台取结果,但是结果还没出来(子线程还没return),你就在前台等着(阻塞),直到你拿到结果(get())你才离开(不再阻塞)。
#include <iostream>
#include <thread>
#include <mutex>
#include<future>
#include<Windows.h>
using namespace std;
double t1(const double a, const double b)
{
double c = a + b;
Sleep(3000);//假设t1函数是个复杂的计算过程,需要消耗3秒
return c;
}
int main()
{
double a = 2.3;
double b = 6.7;
future<double> fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位;
cout << "正在进行计算" << endl;
cout << "计算结果马上就准备好,请您耐心等待" << endl;
cout << "计算结果:" << fu.get() << endl;//阻塞主线程,直至异步线程return
//cout << "计算结果:" << fu.get() << endl;//取消该语句注释后运行会报错,因为future对象的get()方法只能调用一次。
return 0;
}
shared_future
future与shard_future的用途都是为了占位,但是两者有些许差别。
future的get()成员函数是转移数据所有权;shared_future的get()成员函数是复制数据。
因此:
future对象的get()只能调用一次;无法实现多个线程等待同一个异步线程,一旦其中一个线程获取了异步线程的返回值,其他线程就无法再次获取。
shared_future对象的get()可以调用多次;可以实现多个线程等待同一个异步线程,每个线程都可以获取异步线程的返回值。
future | shared_future | |
---|---|---|
语义 | 转移 | 赋值 |
可否多次调用 | 否 | 可 |
多线程安全的懒汉式单例
懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。
饿汉:由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
// 懒汉
Singelton *Singelton::GetSingelton(){
//双检锁,防止频繁的开关锁
if(single == nullptr){
pthread_mutex_lock(&mutex);
if(single == nullptr){
single = new Singelton;
}
pthread_mutex_unlock(&mutex);
}
return single;
}
线程同步
线程之间通信的两个基本问题是互斥和同步。
- 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
- 线程互斥是指对于共享的操作系统资源(指的是广义的”资源”,而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。
上面的原子操作和线程锁就是互斥
在WIN32中(区别于Linux,其实也差不多),同步机制主要有以下几种:
- 事件(Event)
- 信号量(semaphore)
- 互斥量(mutex)
- 临界区(Critical section)
线程池
线程池基础知识
不采用线程池时:
创建线程 -> 由该线程执行任务 -> 任务执行完毕后销毁线程。即使需要使用到大量线程,每个线程都要按照这个流程来创建、执行与销毁。
虽然创建与销毁线程消耗的时间 远小于 线程执行的时间,但是对于需要频繁创建大量线程的任务,创建与销毁线程 所占用的时间与CPU资源也会有很大占比。
为了减少创建与销毁线程所带来的时间消耗与资源消耗,因此采用线程池的策略:
程序启动后,预先创建一定数量的线程放入空闲队列中,这些线程都是处于阻塞状态,基本不消耗CPU,只占用较小的内存空间。
接收到任务后,线程池选择一个空闲线程来执行此任务。
任务执行完毕后,不销毁线程,线程继续保持在池中等待下一次的任务。
线程池所解决的问题:
(1) 需要频繁创建与销毁大量线程的情况下,减少了创建与销毁线程带来的时间开销和CPU资源占用。(省时省力)
(2) 实时性要求较高的情况下,由于大量线程预先就创建好了,接到任务就能马上从线程池中调用线程来处理任务,略过了创建线程这一步骤,提高了实时性。(实时)
线程池的实现
待更新。
其他操作
//让线程等待3秒
this_thread::sleep_for(chrono::seconds(3));
//当前线程“放弃”执行,让操作系统调度另一线程继续执行。过一会儿再来继续执行
this_thread::yield();
//线程id
cout << this_thread::get_id() << endl;