多线程
成都创新互联公司制作网站网页找三站合一网站制作公司,专注于网页设计,网站设计制作、网站设计,网站设计,企业网站搭建,网站开发,建网站业务,680元做网站,已为上千余家服务,成都创新互联公司网站建设将一如既往的为我们的客户提供最优质的网站建设、网络营销推广服务!线程不是越多越好,每个线程有有一个独立的堆栈空间1M.线程切换需要保存很多中间状态
商用程序的必须要求
并发的实现方法
多进程并发:进程通信(同一电脑-文件、管道、共享内存、消息队列;不同电脑-socket)
多线程并发: 单个进程,创建多个线程。通信(共享地址、指针、引用、全局变量)
多线程开销 < 多进程开销
多线程数据共享
只取数据:是安全的,不需要特别处理
有读有写:程序如无其他处理,程序崩溃,简单处理是读写不同步
共享数据保护:使用mutex互斥锁、信号量、边界条件
互斥量mutex与lock_guard
某个线程锁住、操作数据、解锁,其他线程等待解锁、锁住、操作数据、解锁的步骤
lock(),加锁,只有一个线程能加锁成功,成功就返回,没加锁成功就尝试加锁,unlock()解锁
lock() 操作共享数据 unlock() 的操作步骤
lock()与unlock()成对使用,先lock(),后unlock(),之间为数据操作
std::lock_guard守卫者职能,可以同时替换lock与unlock,不能与lock或者unlock同时出现。lock_guard的构造函数执行lock,析构函数执行unlock,使用lock_guard的提前解锁使用{}作用域,结束其生命周期
死锁:至少两个互斥量(即两个共享数据),两个线程都使用这两个互斥量,线程A先使用互斥A,线程2先使用互斥B
死锁的解决方案:
1.多个线程使用互斥量的顺序一样
2.超时放弃
3.std::lock(),一次锁住多个互斥量,一旦没锁住所有互斥量,就释放已锁住的互斥量
std::lock(mutex1,mutex2......)
std::lock()与std::lock_guard配套使用
std::adopt_lock起一个标记作用,表示互斥量已将被lock,在构造时不再被lock
自带超时的互斥量 std::timed_mutex
try_lock_for()尝试锁定互斥,若互斥在指定的时限时期中不可用则返回false, 否则返回true
try_lock_until()尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回false, 否则返回true
if (mutex.try_lock_for(100ms))
{//等待100ms,如果拿到,继续if流程if}
else{//没有拿到,继续else流程}
if (test_mutex.try_lock_until(nowTime + 10s))
{//等待到12.10.10秒,如果拿到,继续if流程}
else{//没有拿到,继续else流程}
unique_lock
比lock_guard灵活,内存开销大点,效率低点
std::unique_lockmunique(mlock);
第二参数unique_lock也可以加std::adopt_lock参数,表示互斥量已经被lock,不需要再重复lock。该互斥量之前必须已经lock,才可以使用该参数。
std::unique_lockmunique(mlock,adopt_lock);//标记mlock已将加锁
第二参数std::try_to_lock避免一些不必要的等待,会判断当前mutex能否被lock,如果不能被lock,可以先去执行其他代码。这个和adopt不同,不需要自己提前加锁。
std::unique_lockmunique(mlock, std::try_to_lock);
第二参数std::defer_lock这个参数表示暂时先不lock,之后手动去lock,但是使用之前也是不允许去lock。一般用来搭配unique_lock的成员函数去使用。下面就列举defer_lock和一些unique_lock成员函数的使用方法。
std::unique_lockmunique(mlock, std::defer_lock);
//当使用了defer_lock参数时,在创建了unique_lock的对象时就不会自动加锁
munique.lock();//手动加锁
//....
munique.unlock();//手动解锁,这里可以不用unlock,可以通过unique_lock的析构函数unlock
try_lock()和上面的try_to_lock参数的作用差不多,判断当前是否能lock,如果不能,先去执行其他的代码并返回false,如果可以,进行加锁并返回true
std::unique_lockmunique(mlock, std::defer_lock);
if (munique.try_lock() == true) {}
release()解除unique_lock和mutex对象的联系,并将std::mutex对象的指针返回出来。如果之前的mutex已经加锁,需在后面自己手动unlock解锁
std::unique_lockmunique(mlock); // 这里是自动lock
std::mutex *m = munique.release();
//TODO
m->unlock();
unique_lock的所有权传递对越unique_lock的对象来说,一个对象只能和一个mutex锁唯一对应,不能存在一对多或者多对一的情况,不然会造成死锁的出现。所以如果想要传递两个unique_lock对象对mutex的权限,需要运用到移动语义或者移动构造函数两种方法,不能复制所有权。
//移动语句
std::unique_lockmunique1(mlock);
std::unique_lockmunique2(std::move(munique1));
// 此时munique1失去mlock的权限,并指向空值,munique2获取mlock的权限
//类的成员函数,返回临时变量,调用移动构造
std::unique_lockrtn_unique_lock()
{
std::unique_locktmp(mlock);
return tmp;
}
std::unique_lockmunique2 = rtn_unique_lock();
条件变量::std::condition_variable条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
一个线程因等待"条件变量的条件成立"而挂起;
另外一个线程使"条件成立",给出信号,从而唤醒被等待的线程。
条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁只能是 std::unique_lock类,配合while使用(注意处理虚假唤醒)。
while(true)
{
std::unique_locklock(mMutex);
mCondition.wait(lock, [this] {
if (!ls.empty()) {
return true;
}
else {
return false;
}
});
//走到这里,互斥锁一定锁住的
//TODO
lock.unlock();//unique随时解锁
};
上面提到的两个步骤,分别是使用以下两个方法实现:
等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until。
给出信号使用的是condition_variable类成员notify_one或者notify_all函数。
线程的阻塞是通过成员函数wait()/wait_for()/wait_until()函数实现的,wait 导致当前线程阻塞直至条件变量被通知,若任何线程在 *this 上等待,则调用 notify_one 会解阻塞(唤醒)等待线程之一。
临界条件(windows临界区)
临界区(Critical Section) 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线 程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 作共享资源的目的。 临界区包含两个操作原语: EnterCriticalSection() 进入临界区 LeaveCriticalSection() 离开临界区 EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的 LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
类似于C++的mutex,mutex在同一线程不能被多次连续lock,std::recursive_mutex递归式互斥量能被连续多次lock()
同一线程,windows的临界区可以被多次连续调用(进去几次,离开几次)
EnterCriticalSection(&winFlag);//进去临界区
EnterCriticalSection(&winFlag);//进去临界区
//TODO
LeaveCriticalSection(&winFlag);//离开临界区
LeaveCriticalSection(&winFlag);//离开临界区
使用临界区的步骤:
a申请一个临界区变量 CRITICAL_SECTION gSection;
b初始化临界区 InitializeCriticalSection(&gSection);
c使用临界区 EnterCriticalSection(&gSection);
d离开临界区LeaveCriticalSection(&gSection);
e释放临界区 DeleteCriticalSection(&gSection);
class C {
public:
C() {
InitializeCriticalSection(&winFlag);//初始化临界区
}
~C() {
DeleteCriticalSection(&winFlag);//释放临界区
}
public:
void test() {
for (int i = 0; i< 2000; ++i) {
EnterCriticalSection(&winFlag);//进去临界区
cout<< "插入一个元素 i = "<< i<< endl;
ls.push_back(i);
LeaveCriticalSection(&winFlag);//离开临界区
}
}
}
线程池
同一调度管理,循环利用的线程的方式,更加稳定
实现方式:在程序启动时,一次性创建多个线程,一般来说程序最多开2000个线程,通常在500线程内
使用场景:
1.单位时间内处理任务频繁而且任务处理时间短;
2. 对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
线程池的组成:
线程池管理器:初始化和创建线程,启动和停止线程,调配任务;管理线程池
工作线程:线程池中等待并执行分配的任务
任务接口:添加任务的接口,以提供工作线程调度任务的执行。
任务队列:用于存放没有处理的任务,提供一种缓冲机制,同时具有调度功能,高优先级的任务放在队列前面
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧