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,其实也差不多),同步机制主要有以下几种:

  1. 事件(Event)
  2. 信号量(semaphore)
  3. 互斥量(mutex)
  4. 临界区(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;  
posted @ 2022-04-14 15:29  二律背反GG  阅读(173)  评论(0编辑  收藏  举报