C11读书笔记8多线程使用简介

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,方便线









































北京治疗白癜风口碑最好的医院
北京白癜风医院排名



转载请注明:http://www.nydjfy.com/xxzl/1549.html

  • 上一篇文章:
  •   
  • 下一篇文章: