多线程学习基础

C++11的标准库中提供了多线程库:

管理线程:

#include <thread>
thread th1;
函数功能
thread()创建线程函数:普通函数、类成员函数对象,lambda表达式
join线程阻塞,等待子线程完成、汇合
detach线程分离,失去线程所有权
joinable检查是否还拥有线程所有权,true有,false无
get_id获取线程id
sleep_for休眠

互斥:

#include <mutex>
mutex x1;
shared_mutex x2;
mutex函数功能
lock锁定指定的互斥量mutex对象,可能会阻塞
unlock解锁
try_lock试图去lock mutex对象,但是不会产生阻塞

在进入临界区之前对互斥量加锁lock,退出临界区时对互斥量解锁unlock
调用该函数后,调用函数会锁定mutex,在有些情况下调用函数会阻塞

  • 如果mutex当前没有被任何其他线程locked,则调用线程lock这个mutex对象
  • 如果mutex目前被其他线程locked,则当前线程阻塞直到mutex被其他线程unlock
  • 如果mutex目前被当前线程lock,则会产生死锁错误。大部分情况会崩溃,因为mutex不支持递归

try_lock:尝试锁定互斥锁。如果互斥锁被其他线程占有,则当前调用线程也不会被阻塞,而是由该函数调用返回false;如果该互斥锁已经被当前调用线程锁住,则会产生死锁

在C++14中提供了shared_mutex解决读写锁问题,同时只能有一个写者 or 同时有多个读者

shared_mutex函数功能
lock_shared共享所有权锁定互斥,若互斥不可用则阻塞
unlock_shared解锁互斥,共享所有权
try_lock_shared尝试加共享锁,互斥不会产生阻塞

lock_shared共享所有者是有上限的,限制最多有多少个对象可以拥有共享所有权,如果超出上限值,阻塞执行,直至共享所有者的数目减少

  • 所以shared_mutex和mutex分别构成读锁和写锁,其中lock_shared和unlock_shared进行读者的锁定与解锁,lock和unlock进行写者的锁定与解锁

可以使用lock_shared()代替lock()unlock()

lock_shared函数功能
lock_shared创建即加锁,作用域结束自动解锁

lock_shared
lock_shared可以理解为一个类,在构造函数中加锁,在析构函数中解锁,lock_shared利用的是c++中的 RAII 语法(Resource Acquisition Is Initialization 资源获取就是初始化),C++标准保证任何情况下,已构造的对象最终会销毁。智能指针就是利用RAII语法。

  • 通过利用使用{ }来调整作用域范围,使互斥量在适合的地方被解锁,如下
void threadmain(int a)
{
    {
        lock_guard<mutex> g2(m);
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//通过使用{}来调整作用域范围,可使得 m 在合适的地方被解锁
    cout << "作用域外的内容3" << endl;
    cout << "作用域外的内容4" << endl;
    cout << "作用域外的内容5" << endl;
}

unique_lock
unique_locklock_guard类似,都可以做到自动加锁和解锁,但是lock_guard使用之后不能手动lock()和手动unlock(),unique_lock可以手动lock()和手动unlock()
std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点,付出性能成本

unique_lock 的第二个参数
adopt_lock表示互斥量已经被Lock了,必须把互斥量提前Lock,否则会报异常
try_to_lock尝试用 mutex的lock()去锁定mutex,但是没有锁定成功,也会立即返回,并不会阻塞
defer_lock初始化了没有加锁的mutex

使用adopt_lock之前必须先lock

my_mutex.lock();
//要先lock(),后续才能用unique_lock的std::adopt_lock参数
std::unique_lock<std::mutex> uniquelock_test(my_mutex,std::adopt_lock);

使用try_to_lock需要使用uniquelock_test.owns_lock()测试是否拿到锁,因为没有锁定成功,不会阻塞

std::unique_lock<std::mutex> uniquelock_test(my_mutex,std::try_to_lock);
	if (uniquelock_test.owns_lock())
	{
		//拿到了锁
		//...
		//其他处理代码
	}
	else
	{
		//没拿到锁
		//...
		//其他处理代码
	}

使用defer_lock之前不能加锁,并且之后要手动加锁,因为初始化的没有加锁的mutex

std::unique_lock<std::mutex> uniquelock_test(my_mutex, std::defer_lock);
//不能自己先lock,否者会出现异常
//之后需要手动 lock,unlock在析构函数中自动执行了

unique_lock可以把拥有的所有权,可以转移给其他的unique_lock对象,所有权转移,mutex可以转移,但是不能复制

	mutex m;
	std::unique_lock<mutex> m1(m,defer_lock);
	std::unique_lock<mutex> m2(std::move(m2));//所有权转移,此时由m2来管理互斥量m
	m2.lock();
    m2.unlock();
    m2.lock();
}

递归锁 std::recursive_mutex
允许同一线程多次锁定,最后一次unlock时释放lock;
定时递归锁 std::recursive_timed_mutex

定时的互斥锁 std::time_mutex
std::timed_mutexstd::mutex 多了两个成员函数:

timed_mutex函数功能
try_lock_for()函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回
try_lock_until()函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回 false

条件变量:condition_variable
多线程通信同步使用,使其他线程等待或者通知继续

#include <condition_variable>

是用来同步线程,利用notify_one为通知,其他接收者线程调用wait(),进入阻塞状态。只要发送者线程一调用notify_one,接收者就开始行动,不再阻塞。

condition_variable函数功能
notify_one通知一个等待的线程不再阻塞,多个线程阻塞,仅通知一个,不具体哪一个
notify_all通知所有等待的线程不再阻塞
wait阻塞当前线程,直到条件变量被唤醒
wait_for阻塞当前线程,直到条件变量被唤醒,或者到定时限长
wait_until阻塞当前线程,直到条件变量被唤醒,或者到达指定时刻

wait函数

  1. wait(unique_lock <mutex>&lck)
    当前线程的执行会被阻塞,直到收到 notify 为止。
  2. wait(unique_lock <mutex>&lck,Predicate pred)
    当前线程仅在pred=false时阻塞;如果pred=true时,不阻塞
    wait()可依次拆分为三个操作:释放互斥锁、等待在条件变量上、再次获取互斥锁
mutex my_mutex;
condition_variable cond;
bool pred = false;

thread1  线程1{
	my_mutex.lock();	    				---1
	cond.wait(my_mutex,pred);
	while(!pred())							---2         
		等待signal,并调用locker.unlock()		---3
		接受signal返回并调用locker.lock()		---4
}
________________________________________

thread1  线程2{
	my_mutex.lock();							---5
	pred = true;								---6
	cond.notify_one();                          ---7
	//cond.notify_all();				
	my_mutex.unlock();					    	---8
}

无论thread1还是thread2先启动,都能实现想要的效果,先看看假设thread1先启动,执行流程:1,2,3,此时thread1进入block状态等待signal,thread2无论在1,2,3的哪一步启动,启动后执行流程都是5,6,7,8,然后thread1执行4,然后进行下一轮循环,判定条件成功,跳出循环,进入下一步。
假设thread2先启动,执行流程为:5,6,7,8,此时无乱thread1在哪一步启动,执行顺序都为1,2,thread1在进行第一轮条件判断时直接跳过等待进入下一步。
加锁my_mutex.lock()的作用:1 保护pred资源,避免多线程同时读写该资源、2 保证等待线程中pred的读操作和wait操作的原子性

Futures:
多线程异步使用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值