赞
踩
例题 (简单)leetcode 1114
主要代码参考:https://leetcode.cn/problems/print-in-order/comments/490373
atomic(原子对象) < condition_variable(条件变量) < posix semaphore(信号量) < mutex (互斥锁)< promise
atomic 汇编级别操作,主要是防止一个单元被读-修改(不同方式)-写的时候被两个进程写导致错误而出现
读-修改-写三个动作变成一个,不能中断
每个 std::atomic 模板的实例化和全特化定义一个原子类型。若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义。
另外,对原子对象的访问可以建立线程间同步,并按 std::memory_order 所对非原子内存访问定序。
std::atomic 既不可复制亦不可移动。
atomic: https://zhuanlan.zhihu.com/p/463913671
https://blog.csdn.net/Cdreamfly/article/details/123122341
https://cloud.tencent.com/developer/section/1008933
https://blog.csdn.net/liuhhaiffeng/article/details/52604052
class Foo { private: std::atomic<bool> firstAtomLock; std::atomic<bool> secondAtomLock; public: Foo() { std::atomic_init(&this->firstAtomLock,false); std::atomic_init(&this->secondAtomLock,false); } void first(function<void()> printFirst) { // printFirst() outputs "first". Do not change or remove this line. printFirst(); firstAtomLock.store(true); } void second(function<void()> printSecond) { while(!firstAtomLock.load()){ std::this_thread::yield(); } // printSecond() outputs "second". Do not change or remove this line. printSecond(); secondAtomLock.store(true); } void third(function<void()> printThird) { while(!secondAtomLock.load()){ std::this_thread::yield(); } // printThird() outputs "third". Do not change or remove this line. printThird(); } };
定义于stdatomic.h
obj - 指向要初始化的原子对象的指针
desired - 用以初始化原子对象的值
此后firstReady 和 secibdReady就变成原子对象,只能通过 **.store(bool)修改,.load(bool)**查询
将当前线程所抢到的CPU”时间片A”让渡给其他线程(其他线程会争抢
是允许多个线程相互交流的同步原语。
它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。
条件变量始终关联到一个互斥。
condition_variable 和 mutex 结合使用,因此condition_variable 更多是为了通知、顺序之类的控制
https://blog.csdn.net/qq_46615150/article/details/114520411?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-114520411-blog-125340274.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-114520411-blog-125340274.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=1 https://blog.csdn.net/TwoTon/article/details/123752423
https://blog.csdn.net/wangxu696200/article/details/122775502
https://blog.csdn.net/ccw_922/article/details/124662275
class Foo { public: Foo() { firstReady = false; secondReady = false; } bool firstReady, secondReady; mutex mtx; condition_variable cv1,cv2; void first(function<void()> printFirst) { lock_guard<mutex> l(mtx); // printFirst() outputs "first". Do not change or remove this line. printFirst(); firstReady = true; cv1.notify_one(); } void second(function<void()> printSecond) { unique_lock<mutex> ul(mtx); cv1.wait(ul, [&]{ return this->firstReady; }); // printSecond() outputs "second". Do not change or remove this line. printSecond(); secondReady = true; cv2.notify_one(); } void third(function<void()> printThird) { unique_lock<mutex> ul(mtx); cv2.wait(ul, [&]{ return this->secondReady; }); // printThird() outputs "third". Do not change or remove this line. printThird(); } };
约等于ul就变成个函数结束自动释放的零时锁,且拥塞时自动释放,约等于升级版锁
m为mutex对象地址
使用mutex时,如果加锁后忘记解锁,或者在使用的过程中出现异常,而没有在异常处理中解锁,那么锁会一直处于加锁状态,而无法解锁。
lock_guard类通过在对象构造的时候对mutux进行加锁,当对象离开作用域时自动解锁,从而避免加锁后没有解锁的问题:
lock_guard不能在中途解锁,只能通过析构时解锁。
lock_guard对象不能被拷贝和移动。
当前线程调用wait()后将被阻塞,直到另外某个线程调用notify_*唤醒当前线程;当线程被阻塞时,该函数会自动调用std::mutex的unlock()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常是另外某个线程调用notify_*唤醒了当前线程),wait()函数也是自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种。
分为待条件和不带条件的,不带条件就说进程卡死了主动unlock被锁的元素,待条件就第二个参数为true是才释放下一语句
notify_one()因为只唤醒一个线程,(这个被唤醒线程一般是队列的第一个线程,这个结论是我自己做实现后感觉的,如果不对,请大家指出来,我更改哈)不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all()。
notify_all()会唤醒所有阻塞的线程,存在锁争用,只有一个线程能够获得锁。那其余未获取锁的线程接着会怎么样?会阻塞?还是继续尝试获得锁?答案是会继续尝试获得锁(类似于轮询),而不会再次阻塞。当持有锁的线程释放锁时,这些线程中的一个会获得锁。而其余的会接着尝试获得锁。
4)unique_lock ul(mtx);
unique_lock中的unique表示独占所有权。
unique_lock独占的是mutex对象,就是对mutex锁的独占。
用法:
(1)新建一个unique_lock 对象
(2)给对象传入一个std::mutex 对象作为参数;
std::mutex mymutex; unique_lock lock(mymutex); 因此加锁时新建一个对象lock; unique_lock lock(mymutex);而这个对象生命周期结束后自动解锁。
unique_lock和lock_guard都不能复制
lock_guard不能移动,但是unique_lock可以移动。
// unique_lock 可以移动,不能复制
std::unique_lock<std::mutex> guard1(_mu);
std::unique_lock<std::mutex> guard2 = guard1; // error
std::unique_lock<std::mutex> guard2 = std::move(guard1); // ok
// lock_guard 不能移动,不能复制
std::lock_guard<std::mutex> guard1(_mu);
std::lock_guard<std::mutex> guard2 = guard1; // error
std::lock_guard<std::mutex> guard2 = std::move(guard1); // error
其实更主要的是cv.wait第一个参数必须是unique_lock,不支持lock_guard
————————————————
信号量 (semaphore) 是一种轻量的同步原件,用于制约对共享资源的并发访问。在可以使用两者时,信号量能比条件变量更有效率。
condition_variable 必须和 mutex 配对使用,semaphore 和 mutex 是可以独立使用的
semaphore 对 acquire 和 release 操作没有限制,可以在不同线程操作;可以仅在线程 A 里面acquire,仅在线程 B 里面 release。mutex 的 lock 和 unlock 必须在同一个线程配对使用;也就是说线程 A 内 mutex 如果 lock了,必须在线程 A 内 unlock,线程 B 内 lock 了,也必须在线程 B 内 unlock。
https://blog.csdn.net/qq_46615150/article/details/114520411
https://blog.csdn.net/weixin_43340455/article/details/125435841
#include<semaphore.h> class Foo { protected: sem_t firstLock; sem_t secondLock; public: Foo() { sem_init(&firstLock, 0, 0); sem_init(&secondLock, 0, 0); } void first(function<void()> printFirst) { // printFirst() outputs "first". Do not change or remove this line. printFirst(); sem_post(&firstLock); } void second(function<void()> printSecond) { sem_wait(&firstLock); // printSecond() outputs "second". Do not change or remove this line. printSecond(); sem_post(&secondLock); } void third(function<void()> printThird) { sem_wait(&secondLock); // printThird() outputs "third". Do not change or remove this line. printThird(); } };
extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
__sem为指向信号量结构的一个指针;
__pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
__value给出了信号量的初始值。
函数sem_post( sem_t *sem )用来增加信号量的值当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。
函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
函数sem_destroy(sem_t *sem)用来释放信号量sem。
单纯的开关锁,关了不继续运行,开了自动竞争,这里写成两个锁,就没有竞争问题了
#include<mutex> class Foo { protected: mutex m1,m2; public: Foo() { m1.lock(); m2.lock(); } void first(function<void()> printFirst) { // printFirst() outputs "first". Do not change or remove this line. printFirst(); m1.unlock(); } void second(function<void()> printSecond) { m1.lock(); // printSecond() outputs "second". Do not change or remove this line. printSecond(); m2.unlock(); } void third(function<void()> printThird) { m2.lock(); // printThird() outputs "third". Do not change or remove this line. printThird(); } };
mutex.lock() mutex.unlock() 一看就懂,不懂自己查
注意初始化为锁定
std::promise是一个类模板,能够在某个线程中给它赋值,然后我们可以再其它线程中把这个值取出来。
stf::future和 promise绑定,用于获取线程返回值,实现线程之间数值交换
初始化: std::promise aaa;
int为保存值的类型指定
future通过.get获取具体值
https://blog.csdn.net/FairLikeSnow/article/details/117905194
class Foo { public: Foo() { } void first(function<void()> printFirst) { // printFirst() outputs "first". Do not change or remove this line. printFirst(); p1.set_value(); } void second(function<void()> printSecond) { // printSecond() outputs "second". Do not change or remove this line. p1.get_future().wait(); printSecond(); p2.set_value(); } void third(function<void()> printThird) { // printThird() outputs "third". Do not change or remove this line. p2.get_future().wait(); printThird(); } private: std::promise<void> p1; std::promise<void> p2; };
promise::set_value()
一个原子函数,用于更改promise对象存储值,且只能修改一次!否则会报错
promise.get_future().wait()
获取promise的future,只有在promise被set后才能获取,进而放行。当作锁用
可以理解为等待资源全收集齐了,才自动开始线程
(测了一下,时间都很短,运行时间波动很大,写的时间是实测最短,所以时间就算参考了
promise(60ms):主要用于线程间数据通信,其“数据准备”可以当线程控制的锁,不知道为啥公认效率最高。。。
mutex(64ms):最简单的锁,一个类中多个线程函数抢一个公用锁,没啥好说的
semaphore(56ms):信号量,个人最喜欢的一种线程控制,效率挺高,比起锁多几个支持的线程
condition_variable(72ms):情况变量,个人理解约等于锁加上自动开关
atomic(160s):底层控制原子锁,看起来是汇编级的,可能因此锁的比较多,速度是最慢的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。