C/C++程序员最苦恼的是自己跨平台能力不是一半弱。如果想跨平台,有一大波函数库等着你来深入研究。你再反观java。。。。
一、原子操作所谓原子操作,就是多线程中“最小的且不可并行化的操作”。通常原子操作都是互斥访问保证的。但是互斥一般靠平台相关汇编指令,这也是为什么C++11之前一直没有做的原因。
?
#include//原子操作需要的头文件#include//线程头文件#includeusingnamespacestd;a_llongtotal{0};//原子数据类型longlong//这样的构词法还有a_int等等//下面的东西不是这一节的内容voidfunc(int){for(longlongi=0;iLL;++i)total=total+i;}intmain(){threadt1(func,0);threadt2(func,0);t1.join();t2.join();couttotalendl;return0;}
可以通过
中查看内置的原子操作。这就出现一个问题。非内置类型始终怎么实现原子操作的。这就是atom模板类,std::at;如:?
aad{12.7f};//这种写法是C++11推荐的
原子操作通常属于“资源型”的数据。这意味着多个线程通常只能访问单个原子类型的拷贝。因此C++11中,原子类型只能从其模板参数中进行构造,标准不允许原子类型进行拷贝构造,移动构造,以及使用operator=等,以防止出现意外。这样无法编译。
aad1{ad};/./错误
?
为了避免线程间关于a的竞争。模板改了很多地方。。
aa;
intb=a;//相当于b=a.load();
inta=1;//相当于a.store(1);
二、线程1.线程对象的创建#include#includeusingnamespacestd;voidfunc(int){}intmain(){threadt1(func,0);t1.join();return0;}线程的构造函数参数,可以视为,(要执行的函数名,该函数参数1,该函数参数2,。。。。)在这里,join是阻塞函数。意义为等上面跑完才开始执行下一个操作。我们如果没有他会怎么样呢?主线程继续往下跑,跑到return0;但此时t1线程可能没有跑完,线程对象却要被强制释放。如下面代码;
#include#includeusingnamespacestd;voidfunc(inta){for(inti=0;i10;++i)std::coutaendl;}intmain(){inta;//停机变量无意义{threadt1(func,1);threadt2(func,2);}//运行到这里,t1,t1没有了std::cina;return0;}如果改成这样,可以正常执行:
intmain(){ inta; { threadt1(func,1); threadt2(func,2); t1.join();//主线程被阻塞了,停在这里,等t1线程对象的线程执行完再运行 t2.join(); } std::cina; return0;}
?
如果不希望线程被阻塞吗,将线程与线程对象分离可以用t1.detach();将线程与线程对象分离。这里要说明下,线程与线程对象是两码事(这个很重要)。我们仅是依托线程对象来创建线程。
如下;
?
intmain(){ inta; { threadt1(func,1); threadt2(func,2); t1.detach();//主线程没有被阻塞,将线程与线程对象分离 t2.detach(); }//运行到这一步,线程对象依然会析构,但是线程却可以继续运行。 std::cina; return0;}这也从侧面描述了,用detach将线程与线程对象分开后,就不能合并了。
?
2.线程的特点线程不能复制,但可以移动(即使用std::move())。线程移动后,线程对象t不再代表任何线程。。
?
另外还可以用std::bind或lambda表达式创建。
?
threadt1(std::bind(func,1)); threadt2([](int,int){},1,2); t1.join(); t2.join();三、互斥量(实质就是锁的变量)
?
互斥量是一种同步原语,线程同步手段,用于保护多线程同时访问共享数据。“互斥量”的翻译十分有迷惑性。它就是“锁类”。以至于如果不这样理解,将会对后面的条件变量混淆。C++11提供了4种互斥量。
1.独占互斥量std::mutex互斥量接口都很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量所有权为止。线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现。try_lock()尝试锁定互斥量,如成功返回true如失败返回false,他是非阻塞的,看来可以用来检查当前互斥量的状态。
改动上面的程序将函数变成加锁的:
?
#include#include#includeusingnamespacestd;std::mutexuni_lock;voidfunc(inta){uni_lock.lock();for(inti=0;i10;++i)std::coutaendl;uni_lock.unlock();}intmain(){inta;{threadt1(func,1);threadt2(func,2);t1.join();t2.join();}std::cina;return0;}
?
这显示这就友好了官方建议尽量使用更安全的lock_guard。因为他在构造时自动加锁,析构时自动解锁,防止忘解锁的事情发生。lock_guard是个类模板,形如其名托管互斥量。后面的几个互斥量基本都用这种方法。
?
std::mutexu_lock;voidfunc(inta){ std::lock_guardlocker(u_lock); for(inti=0;i10;++i) std::coutaendl;}2.递归互斥量std::recursive_mutex
递归锁允许同一线程多次获得该互斥锁,可以用来解决同一个线程需要多次获取互斥量时死锁的问题。
?
需要说明的是,还是尽量不要使用递归互斥量的好
(1)需要用到递归锁定的多线程,往往可以简化为迭代。允许递归容易放纵复杂逻辑产生。
(2)递归锁效率一般低一些。
(3)递归超过一定数目再lock进行调用会抛出std::system错误
3.带超时的互斥量std::timed_mutex与std::recursive_timed_mutex可以看做前两个锁的改进。超时锁,主要用在获取锁时增加超时等待功能,因为有时不知道获取锁需要多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间。我们用try_lock_for,try_lock_until两个接口设置互斥量超时时间。
std::timed_mutexu_lock;voidfunc(inta){ std::chrono::millisecondstimeout(); while(1) { if(u_lock.try_lock_for(timeout)) { ///.../// } }}4.给互斥量上的的两种区域锁
上面我们介绍了lock_guard,这其实是一种区域锁,内部用实现机制是类模板。
std::lock_guard,方便线程对互斥量上锁。
std::unique_lock,方便线
北京治疗白癜风口碑最好的医院北京白癜风医院排名