C++11并发相关知识点

并发的途径:

1.    多进程并发

将应用程序分为多个、独立的、单线程的进程。

它的特点:

(1)这种进程之间的通信通常设置复杂,或是速度较慢,或两者兼备,因为操作系统通常在进程间提供了大量的保护。

(2)运行多个进程所需的开销较大,比如,启动进程需要的时间、操作系统必须投入内部资源来管理进程。

(3)使用独立的进程实现并发还有一个额外的优势,它可以实现通过网络连接的不同机器上运行独立的进程。

 

2.    多线程并发

在单个进程中运行多个线程。线程很像轻量级的进程:每个线程相互独立运行,且每个独立的线程可以运行不同的指令序列。但进程中的所有线程都共享相同的地址空间,并且从所有线程中访问到大部分数据(比如,全局变量),指针、对象的引用或数据可以在线程之间传递。

它的特点:

(1)由于是共享的地址空间,以及缺少线程间的数据保护,使得多线程相关的开销远小于使用多进程,因为操作系统有更少的薄记要做。

(2)共享内存的灵活性是有代价的:如果数据被多个线程访问,那么程序员必须确保当每个线程访问时所看的数据是一致的。

 

1.    C++标准没有为进程间通信提供任何原生支持,所以使用多进程的应用程序将不得不依赖平台的相关的API来实现。

2.    启动单一进程中的多线程并在其间进行通信的开销要比启动多个单线程进程的通信开销更低。

3.    在应用程序中使用并发的原因有两个:关注点分离和性能。不使用并发的唯一原因就是在收益比不上成本的时候。

为了划分关注点而使用并发:

(1)通过将相关的代码放在一起并将无关的代码分开,这种方法可以使你的程序更容易理解和测试,减少出错的可能。

(2)通过并发来分隔不同的功能区域,即使在这些不同功能区域的操作需要在同一时刻发生的情况下。

(3)在有些情况下,线程的数量与CPU可用的内核的数量无关,因为对线程的划分是基于概念上的设计而不是试图增加吞吐量。

如果软件想要利用日益增长的计算能力,它必须设计为并发运行多个任务。

为了性能而使用并发:

(1)将单一任务划分几部分且各自并行运行,从而降低总运行时间,这就是任务并行(taskparallelism)。在各个部分可能存在很多的依赖,区别可能在过程方面:一个线程执行算法的一部分而另一个线程执行算法的另一部分。可能在数据方面:每个线程在不同的数据部分上执行相同的操作,这种方法称为数据并行(data parallelism)。

(2)使用可用的并行方式来解决更大的问题。与其同时处理一个文件,不如酌情处理2个或10个或20个或更多。

4.    由于对于一个给定的执行线程,join()只能被调用一次,因此,在调用join()之前需判断该执行线程是否可以调用join(),使用的判断函数joinable()。

std::thread::thread(),构造函数,用于创建线程。

std::thread::join(),用于等待线程运行结束。

std::thread::detach(),用于让线程不受句柄控制。 

std::thread::swap(),用于交换两个线程。

std::thread::get_id(),用于获得线程ID号。 

std::thread::joinable(),用于判断线程是否可等待。

std::thread::native_handle(),用于获得与操作系统相关的原生线程句柄。

 

5.    当一个类不需要复制构造函数和复制运算符时,可以在类中对这两个函数标记为delete,这样可以阻止编译器自动提供这两个函数。

6.    传递参数给线程函数时,参数会以默认的方式被复制到内部存储空间,在那里新创建的执行线程可以访问它们,即便函数中的相应参数期待着引用。

7.    在C++标准库里许多拥有资源的类型,比如,std::thread,std::ifstream和std::unique_ptr是可移动的(movable),而非可复制的(copyable)。这意味着,一个特定执行线程的所有权可以在std::thread实例之间移动。

8.    当给线程函数传递一个函数对象时,要考虑的一件事是所谓的“C++的最棘手解析”。如果你传递一个临时的且未命名的变量,那么其语法可能与函数声明一样。例如,

class background_task  //类定义

{

public:

   void operator ()() const

   {

      do_something();

     do_something_else();

   }

};

//下面语句想定义一个线程对象,但未能所愿,因为编译器更倾向于下面第二种解析。

std::thread my_thread(background_task());

此句有两种解析:

(1)    我们知道,background_task()是创建一个无名的对象,它调用的是默认构造函数。因为该对象所在的类中定义了operator()操作符,所以,无名对象可以充当一个函数作为线程的初始函数。从无名对象创建的角度,此句合法没有问题。

(2)    假设有一个函数声明为std::threadmy_thread(background_task (*pfun)()),其中,函数名为my_thread, 返回类型为std::thread,函数参数为一个函数指针,函数指针名为pfun,函数指针的返回类型为background_task,同时不接受任何参数。因为函数的声明中形参名允许省略,编译器不进行对形参名检查,于是,上述的函数声明简写为std::thread my_thread(background_task ()),其参数类型是指向不接受参数同时返回background_task对象的函数的指针。从函数声明的角度,此句合法没有问题。

解决办法,通过一组额外的括号,或使用新的统一初始化语法,例如:

std::threadmy_thread( (background_task()) );

std::threadmy_thread{ background_task() };

有一种避免上述问题,同时同样可以创建线程对象的等价方法,就是用lambda表达式代替上述函数对象。如下:

//创建了一个无名的未捕获局部变量的无参数的lambda表达式。

std::threadmy_thread( [ ] (){

   do_something();

   do_something_else();

} );

 

9.    一旦开始了线程,你需要显示的决定是等待它完成(通过join()等待),还是让它自行运行(通过detech()分离)。如果你在std::thread对象被销毁前未作决定,那么你的程序会被终止(std::thread的析构函数调用std::terminate())。因此,即便是在异常的情况下,确保线程正确的结合或分离都是你的当务之急。需要注意的是,你只需要在std::thread对象被销毁之前作出决定即可(std::thread对象所开启的线程,它本身可能在你的结合或分离它之前早就已经结束了,如果你选择的是分离(detech分离,这就意味着,打破了该线程与std::thread对象的联系并确保当std::thread对象被销毁时std::terminate()不会被调用,即使该线程仍在后台运行),那么该线程可能在std::thread对象被销毁后很久都还在运行;如果你选择的是结合(join()等待),那么该线程的终止可能随对象的销毁而停止,也可能跟对象的销毁无直接关系(这是本人推测的结论))。

 

10.  RAII惯用法:C++资源管理的利器

RAII是C++语言中的一个惯用法(idiom),全称Resource Acquireis initialization,中文翻译“资源获取就是初始化”。Bjarne曾写道:使用局部对象管理资源的技术通常称为“资源获取就是初始化”。这种通用技术依赖于构造函数和析构函数的性质以及它们与异常处理的交互作用。

其基本原理:既然类是C++的主要抽象工具,那么就将资源抽象成类,用局部对象来表示资源,把管理资源的任务转化为管理局部对象的任务,而管理局部对象的任务很简单,因为它的创建和销毁工作是系统自动完成的。我们只需在某个作用域内定义局部对象,当该对象离开该作用域之后,善后工作由系统调用其析构函数,从而销毁该对象。

换句话说,RAII就是用对象表示资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造函数和析构函数对应起来,从而保证在对象的生命周期内资源始终有效,对象销毁时,资源必被释放。由此可见,RAII惯用法是资源管理的有力工具,C++程序员依靠RAII写出的代码不仅简介优雅,而且做到了异常安全。

比如,智能指针就是通过RAII来确保内存资源的安全;还有标准C++库提供的std::lock_guard类模板,它也是通过RAII来确保互斥元这种资源能够自动获取和释放(它在构造时,锁定所给的互斥元,在析构时将互斥元解锁,从而该互斥元始终能够被正确解锁)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值