栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

多线程学习基础

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程学习基础

C++11的标准库中提供了多线程库: 管理线程:
#include 
thread th1;
函数功能
thread()创建线程函数:普通函数、类成员函数对象,lambda表达式
join线程阻塞,等待子线程完成、汇合
detach线程分离,失去线程所有权
joinable检查是否还拥有线程所有权,true有,false无
get_id获取线程id
sleep_for休眠
互斥:
#include 
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 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_lock与lock_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 uniquelock_test(my_mutex,std::adopt_lock);

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

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

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

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

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

	mutex m;
	std::unique_lock m1(m,defer_lock);
	std::unique_lock 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_mutex 比 std::mutex 多了两个成员函数:

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

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

#include 

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

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

wait函数

  1. wait(unique_lock &lck)
    当前线程的执行会被阻塞,直到收到 notify 为止。
  2. wait(unique_lock &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:
多线程异步使用

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/443992.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号