C++多线程

并发,线程,进程的基本概念

并发
并发:两个或多个任务(独立的活动)同时发生(进行),
目的:可以执行更多的任务,提高性能
thread 类
thread 创建线程有三种方式, 
	(1) 创建线程传递函数入口地址
	(2) 创建线程传递仿函数对象
	(2) 创建线程传递lambda 对象
join(); //阻塞等待对应的线程结束,执行join的线程才会继续往下执行
detach();	//线程分离,让detach() 对应的线程脱离 执行 detach() 的线程。
joinable();	//判断 该对象的线程是否可以等待, 返回 true 表示可以等待,否则不可以等待(之前调用已经 detach() )。
this_thread 命名空间
this_thread::get_id() ; //获取线程 ID
this_thread::sleep_for();	//c++标准睡眠平台
ref() 函数
thread myThread(func, std::ref(obj)); // func 为函数入口, obj 为 对象,则表示不需要拷贝obj对象到新的线程中,而是在新线程中 创建一个引用对象,引用obj对象。
函数入口
注意点: thread myThread(func, arg, pMyBuf); // func 为函数入口, agr 为值传递,pMyBuf为指针传递
	// 在该过程中, 不管是引用传递还是值传递,都会在新线程中拷贝一份对应的对象。 但是 pMyBuf 指针传递则是传递 指针值,所有 我们在指针传递过程中不能传递 指针指向 栈内存的地址( char buf[] 这种为栈中的内存, string 类的字符串不在栈中,如果想要将栈中的字符串传递到新线程中,可以在函数入口创建 string 的临时对象,”不要让编译器自动隐士转换,会存在可能栈中的buf释放了,才进行隐士转换问题“)
总结:创建对象的过程中,主线程先将参数a1对象拷贝一份a2对象放到新线程中(a2诞生在主线程),等到到新线程入口函数执行后,新线程将参数a2传递给函数入口参数,但是入口函数执行结束后a2对象的析构函数是在新线程执行(a2结束在新线程)。
仿函数对象
注意点:thread myThread(functor); // functor 为仿函数对象,
	//在该过程中,创建新线程,在新线程中会存在 functor 对象的拷贝,所有functor对象内部数据最好不要用到指针和引用 
类的成员函数作为入口
thread myThread( &A::fun, a, 10); // fun 为A类的成员函数, a 为 A类的对象, 10 为成员函数的参数
lambda对象
auto lambda = [] { cout << "lambda"<< endl;};
thread myThread(lambda );

全局变量问题

全局变量诞生在main()函数执行之前,当main()直接结束会结束全局变量的声明周期。
在多线程中,如果主线程结束生命周期后,其他线程不能使用全局变量。

mutex类 & lock_guard模板类

mutex
mutex用来对共享资源得互斥,lock(), unlock()必须成对存在
	mutex myMutex;
	myMutex.lock();
	....保护数据
	myMutex.unlock();
lock_guard
{	
	// lockGuard对象一旦创建,内部执行 lock()
	lock_guard<mutex> lockGuard(myMutex); //myMutex 为互斥量对象
	.... 保护数据
	//lockGuard 结束声明周期则会执行 unlock();  
	//所以作用域的使用,是 lock_guard的关键
}
lock() 函数模板
lock() 可以锁住多个互斥量,导致多个互斥量不会死锁的问题,
	// 其原理应该是能将多个互斥量都加锁,才会全部加锁,不然一个都不加锁。
mutex mutex1, mutex2;
lock(mutex1, mutex2);	//该函数接受不定参数
... 保护数据
mutex1.unlock();		//解锁要自己解锁
mutex2.unlock();
lock() 函数 与 lock_guard 模板类配合使用
mutex mutex1, mutex2;
{
	lock(mutex1, mutex2);	//该函数接受不定参数
	lock_guard<mutex> lockGuard1(mutex1, std::adopt_lock);  	// std::adopt_lock 表示创建不加锁
	lock_guard<mutex> lockGuard2(mutex2, std::adopt_lock);
	... 保护数据
}

unique_lock 类模板

{
	mutex mutex1;
	unique_lock<mutex> uniqueLock(mutex1); // unique_lock 与 lock_guard 用法相似
	...保护数据
}		
第二参数有:
	std::adopt_lock 	创建不加锁,前提是mutex提前lock();
	std::try_to_lock   	尝试加锁,不管加不加锁该对象都会创建成功,通过 owns_lock() 判断是否加锁成功。
	std::defer_lock 	初始化一个没有加锁的mutex,
函数
lock();
unlock();
try_lock();
release();	//返回对象指针,将mutex 和 unique_lock 解除。
所有权的传递
unique_lock 不可以复制,但是可以移动,移动的过程不会再次加锁

单例共享数据

单例模式
class MyA
{
public:
	volatile static MyA* createSingle(int i) {
		if (single == nullptr) {
			lock_guard<mutex> lockGuard(myMutex);
			if (single == nullptr) {
				single = new MyA(i);
				//程序退出,删除单例对象使用到
				static DelMyA s_delMya;
			}
		}
		return single;
	}

	class DelMyA {	//类中嵌套对象,用来释放对象,前提条件是 主线程退出,其他线程不能使用单例对象
	public:
		~DelMyA() {
			if (MyA::single != nullptr) {
				delete MyA::single;			// 类内得数据可以相互访问
				MyA::single = nullptr;
				cout << "释放单例程对象内存" << endl;
			}
		}
	};

	int getSignleData() volatile {
		return m_i;
	}
private:
	MyA(int i) : m_i(i) { cout << "构造函数执行" << endl; }

	int m_i;
	volatile static  MyA *single;
	static mutex myMutex;
};
mutex MyA::myMutex;
volatile MyA *MyA::single = nullptr;
call_once() 函数
void (call_once)(once_flag& _Flag, _Fn&& _Fx, _Args&&... _Ax);
	保证调用的函数只调用一次。
	比互斥量消耗资源更少。

condition_variable 类

mutex myMutex;
condition_variable cond;
......
函数等待条件:
{
	unique_lock<mutex> uniqueLock(myMutex);
	// 带函数的wait用法
	cond.wait( uniqueLock, [this] { 
		if(this->flag)
			return true;
		return false;
	});
	// 不带函数的wait 用法
	cond.wait(uniqueLock);
	...执行等待条件成立
}
	
将等待条件唤醒
{
	unique_lock<mutex> uniqueLock(myMutex);
	...执行条件成立的动作
	cond.notify_one(); 或 cond.notify_all();
}
wait
1.不带函数时,将互斥量解锁,阻塞等待条件通知,
2. 带函数时,当函数返回false 时,表示条件不成立,将互斥量解锁,阻塞等待条件通知,函数返回 true 时,表示条件成立,则直接往下执行。
3.收到通知唤醒后尝试对互斥量进行加锁,加锁成功后(不带函数(退出wait(),往下执行) 或是带函数(执行 2 步骤)),往下执行;否则,继续阻塞等待条件通知。
notify_one 和 notify_all
notify_one() 唤醒正在阻塞的线程,但是当没有线程阻塞,notify_one() 执行没有效果,该函数是将阻塞等待的线程消息队列出队一个线程,将其唤醒,如果阻塞等待的消息队列没有线程,也就是不会有线程被唤醒。
notify_all() 是将所有阻塞等待的线程消息队列出队,依次唤醒。

async 函数模板 和 future 类模板

async()
async() 参数的传递以及线程函数入口参数的传递,与 thread 类创建一致。
	1.future<type> 的 type 与 线程入口函数的返回要一致。
	2.future::get()会阻塞等待线程执行结束返回结果。
	3.future::wait() 阻塞等待线程返回,但是不在意返回值。
	4.不调用 future成员函数,该程序也会卡在 future对象的析构函数中,等待绑定的线程结束
	
当async() 函数的第一个参数不是 线程入口函数时,而是 std::launch::deferred , 
	1.当与async函数绑定的future 对象调用 get() 或是 wait()时,入口函数才会执行。
	2.注意是入口函数,不是线程入口函数,当async函数的第一个参数是 std::launch::deferred 时,不会创建新线程,而是future对象调用 get() 或是 wait() 时,间接调用入口函数。
	3.当 future 对象不调用 get() 或是 wait() 函数,future对象析构函数不会启动入口函数。
future

在这里插入图片描述

packaged_task 类模板

包装函数当线程入口

在这里插入图片描述

包装lambda表达式当线程入口

在这里插入图片描述

promise 类模板

我们能够在某个线程中它他赋值,然后我们可以再其他线程中,把这个值取出来使用。

在这里插入图片描述

async, packaged_task, promise 和 future

async,packaged_task, promise 的使用都是通过 future 类
async 是 创建一个异步操作,该操作的返回值通过 future 获取,异步操作结束future才能获取到。
packaged_task 可以打包多个任务,任务返回值还是通过 future 获取,多个任务操作结束future 才能获取到。
promise 的作用是消除了 任务返回时才可以的缺点,只要向 promise 设置返回值,future就可以获取到结果,任务还可以继续往下执行

shared_future 类模板

future.get() 获取数据时是使用 move() ,也就是只能获取一次
shared_future.get() 获取数据是拷贝数据,以便多个线程使用该值。

atomic

原子操作:在多线程中不会被打断的程序执行片段
原子操作是一种不需要加锁(无锁)技术的多线程,原子操作的效率比加锁性能更高。
注意:互斥量针对的是代码段,原子操作是针对一个变量。
std::atomic 只针对内嵌变量,一般用于多线程统计。
atomic 支持的运算符
++, --, +=, -=, &=, |=, ~=,是支持

在这里插入图片描述

recursive_mutex

mutex 不可以多次lock,
recursive_mutex 可以多次lock。
recursive_mutex 和以个 lock_guard, unique_lock 配合使用

timed_mutex 和 recursive_timed_mutex

带超时功能的独占互斥量和带超时的递归的独占互斥量
timed_mutex ,recursive_timed_mutex和以个 lock_guard, unique_lock 配合使用,但是超时加锁超时得自己释放锁
try_lock_for(arg);	// arg 表示得是一段时间
try_lock_until(arg) // arg 表示是的是某个时间点。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值