C++11并发编程阅读笔记

thread中join和detach的区别

//https://blog.csdn.net/xibeichengf/article/details/71173543

C++中的thread对象通常来说表达了执行的线程(thread of execution),这是一个OS或者平台的概念。

当thread::join()函数被调用后,调用它的线程会被block,直到线程的执行被完成。基本上,这是一种可以用来知道一个线程已结束的机制。当thread::join()返回时,OS的执行的线程已经完成,C++线程对象可以被销毁。

当thread::detach()函数被调用后,执行的线程从线程对象中被分离,已不再被一个线程对象所表达–这是两个独立的事情。C++线程对象可以被销毁,同时OS执行的线程可以继续。如果程序想要知道执行的线程何时结束,就需要一些其它的机制。join()函数在那个thread对象上不能再被调用,因为它已经不再和一个执行的线程相关联。

去销毁一个仍然可以“joinable”的C++线程对象会被认为是一种错误。为了销毁一个C++线程对象,约么join()函数需要被调用(并结束),要么detach()函数被调用。如果一个C++线程对象当销毁时仍然可以被join,异常会被抛出。

C++线程对象不被表达为执行的线程的其它的情况(也就是unjoinable):

默认构造的线程对象不表达为执行的线程,所以是unjoinable。
被移开的线程将不表达为执行的线程,所以是unjoinable。

在std::thread的析构函数中,std::terminate会被调用如果:
线程没有被Joined(用t.join())
线程也没有被detached(用t.detach())
因此,你应该在执行流程到析构函数前总是要么join,要么detach一个线程。
当一个程序终止时(比如main返回),剩下的在后台的detached线程执行不会再等待;相反它们的执行会被挂起并且它们的本地线程对象会被销毁。
关键地,这意味着这些线程的栈不是完好无损的,因此一些析构函数不会被执行。依赖于这些行为,一些析构函数假象会被承担,这可能是一种坏情形,好像程序已经Crash或者已经被kill。希望OS会释放加在这些文件上的锁。Depending on the actions those destructors were supposed to undertake, this might be as bad a situation as if the program had crashed or had been killed. Hopefully the OS will release the locks on files, etc… but you could have corrupted shared memory, half-written files, and the like.
所以,你应该使用join还是detached?
使用join
除非你需要更灵活并且想要独立地提供一种同步机制来等待线程完成,在这种情况下你应该使用detach

C++模板之typename和class关键字的区别

按 C++ 标准来bai说,template 用于基础数据类型,typename 指类型名,T 可以取 char int double 等。
template 用于类,T 可以取任何类。
但是这里有一个问题,结构体应该用 typename 还是 class? 结构体肯定不是基础数据类型,但也不是类。
所以实际情况是,template 的 T 也可以取基础数据类型,tempate 的 T 也可以取类。
但有一个特例,就是当 T 是一个类,而这个类又有子类(假设名为 innerClass) 时,应该用 template:
typename T::innerClass myInnerObject;
这里的 typename 告诉编译器,T::innerClass 是一个类,程序要声明一个 T::innerClass 类的对象,而不是声明 T 的静态成员,而 typename 如果换成 class 则语法错误。

右值引用

https://www.zhihu.com/question/22111546
右值引用的意义通常解释为两大作用:移动语义和完美转发.

############################
https://www.jianshu.com/p/d19fc8447eaa
所谓完美转发:
所谓转发,就是通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的
###########################

std::move()可以提取对象的右值,而static_cast<X&&>将对应变量转换为右值。

这样显示转换为右值之后,应保证之后不再使用该对象。

右值引用是C++11中最重要的新特性之一,它解决了C++中大量的历史遗留问题,使C++标准库的实现在多种场景下消除了不必要的额外开销(如std::vector, std::string),也使得另外一些标准库(如std::unique_ptr, std::function)成为可能。即使你并不直接使用右值引用,也可以通过标准库,间接从这一新特性中受益。为了更好的理解标准库结合右值引用带来的优化,我们有必要了解一下右值引用的重大意义。右值引用的意义通常解释为两大作用:移动语义和完美转发。本文主要讨论移动语义。移动语义======移动语义,简单来说解决的是各种情形下对象的资源所有权转移的问题。而在C++11之前,移动语义的缺失是C++饱受诟病的问题之一。举个栗子。问题一:如何将大象放入冰箱?答案是众所周知的。首先你需要有一台特殊的冰箱,这台冰箱是为了装下大象而制造的。你打开冰箱门,将大象放入冰箱,然后关上冰箱门。问题二:如何将大象从一台冰箱转移到另一台冰箱?普通解答:打开冰箱门,取出大象,关上冰箱门,打开另一台冰箱门,放进大象,关上冰箱门。2B解答:在第二个冰箱中启动量子复制系统,克隆一只完全相同的大象,然后启动高能激光将第一个冰箱内的大象气化消失。等等,这个2B解答听起来很耳熟,这不就是C++中要移动一个对象时所做的事情吗?“移动”,这是一个三岁小孩都明白的概念。将大象(资源)从一台冰箱(对象)移动到另一台冰箱,这个行为是如此自然,没有任何人会采用先复制大象,再销毁大象这样匪夷所思的方法。C++通过拷贝构造函数和拷贝赋值操作符为类设计了拷贝/复制的概念,但为了实现对资源的移动操作,调用者必须使用先复制、再析构的方式。否则,就需要自己实现移动资源的接口。为了实现移动语义,首先需要解决的问题是,如何标识对象的资源是可以被移动的呢?这种机制必须以一种最低开销的方式实现,并且对所有的类都有效。C++的设计者们注意到,大多数情况下,右值所包含的对象都是可以安全的被移动的。右值(相对应的还有左值)是从C语言设计时就有的概念,但因为其如此基础,也是一个最常被忽略的概念。不严格的来说,左值对应变量的存储位置,而右值对应变量的值本身。C++中右值可以被赋值给左值或者绑定到引用。类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低。右值中的数据可以被安全移走这一特性使得右值被用来表达移动语义。以同类型的右值构造对象时,需要以引用形式传入参数。右值引用顾名思义专门用来引用右值,左值引用和右值引用可以被分别重载,这样确保左值和右值分别调用到拷贝和移动的两种语义实现。对于左值,如果我们明确放弃对其资源的所有权,则可以通过std::move()来将其转为右值引用。std::move()实际上是static_cast<T&&>()的简单封装。

C++中的RAII

//https://blog.csdn.net/quinta_2018_01_09/article/details/93638251?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-5.control&dist_request_id=ef24a780-12b6-421e-b702-8c0a0abe525e&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-5.control

//https://dabaojian.blog.csdn.net/article/details/52133213?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&dist_request_id=ace5b9a2-0ce9-4731-a118-99b35a24b472&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control
RAII是Resource Acquisition Is Initialization的缩写,用普通话将就是”资源获取即初始化”

如何使用RAII?

当我们在一个函数内部使用局部变量,当退出了这个局部变量的作用域时,这个变量也就别销毁了;当这个变量是类对象时,这个时候,就会自动调用这个类的析构函数,而这一切都是自动发生的,不要程序员显示的去调用完成。这个也太好了,RAII就是这样去完成的。

由于系统的资源不具有自动释放的功能,而C++中的类具有自动调用析构函数的功能。如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用程序员显示的去调用释放资源的操作了。

template<class… _Mutexes>
class lock_guard
{ // class with destructor that unlocks mutexes
public:
explicit lock_guard(_Mutexes&… _Mtxes)
: _MyMutexes(_Mtxes…)
{ // construct and lock
_STD lock(_Mtxes…);
}

lock_guard(_Mutexes&... _Mtxes, adopt_lock_t)
	: _MyMutexes(_Mtxes...)
	{	// construct but don't lock
	}

~lock_guard() _NOEXCEPT
	{	// unlock all
	_For_each_tuple_element(
		_MyMutexes,
		[](auto& _Mutex) _NOEXCEPT { _Mutex.unlock(); });
	}

lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;

private:
tuple<_Mutexes&…> _MyMutexes;
};
在使用多线程时,经常会涉及到共享数据的问题,C++中通过实例化std::mutex创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。不过这意味着必须记住在每个函数出口都要去调用unlock(),也包括异常的情况,这非常麻烦,而且不易管理。C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard,其会在构造函数的时候提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁的互斥量总是会被正确的解锁。上面的代码正式>头文件中的源码,其中还使用到很多C++11的特性,比如delete/noexcept等,有兴趣的同学可以查一下

总结
说了这么多了,RAII的本质内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源一定会被释放。说白了,就是拥有了对象,就拥有了资源,对象在,资源则在。所以,RAII机制是进行资源管理的有力武器,C++程序员依靠RAII写出的代码不仅简洁优雅,而且做到了异常安全。在以后的编程实际中,可以使用RAII机制,让自己的代码更漂亮。

pragmaonce与#ifndef --#define区别

#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况

#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。

方式一由语言支持所以移植性好,方式二 可以避免名字冲突
*

C++ 14 17新特性总结

//https://blog.csdn.net/chenweiyu11962/article/details/90578429

c++14函数返回类型auto
二、lambda参数auto
在C++11中,lamdba函数参数需要被声明为具体的类型。C++14放宽了这一要求,允许lambda函数参数类型使用类型说明符auto。
代码简洁,并且可以增强重构。
三、[[deprecated]]
利用 deprecated 杂注,可以指示函数、类型或任何其他标识符不再受将来版本支持,或者不应再使用它们。
#pragma deprecated( identifier1 [,identifier2, …] )
四、constexpr
constexpr表示这玩意儿在编译期就可以算出来(前提是为了算出它所依赖的东西也是在编译期可以算出来的)。而const只保证了运行时不直接被修改(但这个东西仍然可能是个动态变量),const修饰的是类型,constexpr修饰的是用来算出值的那段代码。
在C++11中,使用constexpr声明的函数可以在编译时执行,生成一个值,用在需要常量表达式的地方,比如作为初始化模板的整形参数。C++11的constexpr函数只能包含一个表达式,C++14放松了这些限制,支持诸如if 和switch等条件语句,支持循环,其中包括基于区间(range)的for 循环。

C++17一、auto
从c++11开始,auto关键字能够通过初始化器推导出变量的类型。在c++14中,auto关键字的能力进一步提升,能够通过return语句推导出函数的返回类型。
使用auto关键字能够提高编码效率,同时能够简化重构流程。但是,C++11中的auto推导,往往结果与预期的不同。
二、lamda
lambda也是c++11中引入的,在C++11中,lambda表达式只能用捕获this,this是当前对象的一个只读的引用。
在C++17中,可以捕获this, this是当前对象的一个拷贝,捕获当前对象的拷贝,能够确保当前对象释放后,
lambda表达式能安全的调用this中的变量和方法。
三、条件语句初始化
c++17中支持在 if 或者switch 语句中进行初始化, 这个能力的出现能够让代码更加的简洁。

四、std::optional
std::optional表示一个可能存在的值。 当我们通过函数创建一个对象时,通常使用通过函数返回错误码,而通过出参返回对象本身。
has_value() // 检查对象是否有值
value() // 返回对象的值,值不存在时则抛出 std::bad_optional_access 异常
value_or() // 值存在时返回值,不存在时返回默认值
同时还出存在其他的安全函数

智能指针及编码注意事项

https://blog.csdn.net/haolexiao/article/details/56773039?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-17.control&dist_request_id=bfd5d7c5-30a6-4da7-af5d-485287f4a6fd&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-17.control
这个博客上详细介绍了一些智能指针的陷阱

智能指针的陷阱:
https://blog.csdn.net/y1196645376/article/details/53023848

自我总结的编码注意事项:
1:千万不要用一块非new分配的动态内存去初始化一个智能指针
2:不要使用auto_ptr
3:见措施10
措施1: 各插件保证线程安全(插件内部).—可优化保证
措施2: 操作资源时, 编码时需要判断是否有效且保证资源的线程安全.
措施3: 利用gcc官方内存检测工具asan来检测程序中潜在的崩溃情况.
措施4: 更新库时, 头文件不要遗漏更新, 更新库后, 自测或者跑下自动化用例.
措施5: 公共库编译环境保持稳定, cenos低版本和高版本可能某些系统库不兼容
措施6: 搭建vargrind的稳定性环境, 建议固定周期用memcheck工具检测程序中可能存在泄漏问题
措施7: 确保函数退出和资源反初始化时, 线程执行完毕, 利用join结合线程来确保, 尽量避免访问局部变量的函数中创建线程, 除非能够100%确保线程在函数退出前完成
措施10: 使用智能指针时, 避免直接用已分配内存的裸指针直接赋值, eg : spShare(ptrSource), 因为裸指针ptrSource可能在某个时间点给deleter, 此时智能指针乃在用, 建议: spShare(new T())或 spShare(make_shared()).
措施11: 指针释放后, 置位NULL, 使用指针时, 判断有效性.
措施12: 不要在任何类的构造函数中传递this指针和注册回调函数, 因为此时对象还是半成品.
措施13: 确认开源库或三方库是否线程安全.
措施14: 静态或全局资源, 很难保证释放的先后顺序.
措施15: 需要清楚各类变量, 对象, 对象间的生命周期.

c++11 之emplace_back 与 push_back的区别

//https://blog.csdn.net/p942005405/article/details/84764104

如何使用std::mem_fn

//https://blog.csdn.net/elloop/article/details/50375820
前言
本文总结了STL中函数适配器:mem_fn系列函数的用法,它们是:mem_fun (c++98), mem_fun_ref (c++98), mem_fn (c++11). 文中给出了它们各自的使用范围及代码示例,提到了mem_fn的使用限制,使用bind来解决这个限制。

基本用法
从名字也能看出个大概,mem_fn里面的mem就是指类的成员member, 而fn就是指function, 加在一起就是说member function,即mem_fn是用来适配类的成员函数的,下面从代码里来看一下它们的区别:

mem_fn_and_mem_fun.cpp是我写的一个测试文件,从注释里看,并列的3个部分,分别是:

  1. mem_fun
  2. mem_fun_ref
  3. mem_fn

#include “gtest/gtest.h”
#include “inc.h”
#include
#include

NS_BEGIN(elloop);

using namespace std;
using namespace std::placeholders;

// 定义一个类Foo用来测试,适配其成员函数
// 其中函数pln(x)是输出x,并换行的意思;
class Foo
{
public:
// 无参数member function
void print() { pln(a_); }

// 接受一个参数的member function
void print2(int i) 
{
    pln(a_);
    pln(i);
}

int a_{ 100 };

};

//----------------------- c++98 mem_fun and c++11 mem_fn -----------------------
BEGIN_TEST(FunctorTest, Mem_FunTest, @);

// 1. mem_fun is for a pointer to an obj.
// 定义一个容器,存入几个Foo*, 然后使用mem_fun来取指针并绑定到Foo的成员函数
vector<Foo*> fpv;
fpv.push_back(new Foo());
fpv.push_back(new Foo());
fpv.push_back(new Foo());
fpv.push_back(new Foo());

for_each(fpv.begin(), fpv.end(), mem_fun(&Foo::print));
cr;

// mem_fun_ref用来绑定的target应该为Foo对象,而不是指针
//for_each(fpv.begin(), fpv.end(), mem_fun_ref(&Foo::print)); // error.

for_each(fpv.begin(), fpv.end(), bind(&Foo::print, _1)); // also can use bind
cr;

// 如果成员函数接受额外参数(不仅仅是对象本身), 那么mem_fun就无能为力了,要用bind
//for_each(fpv.begin(), fpv.end(), mem_fun(&Foo::print2, 10));
for_each(fpv.begin(), fpv.end(), bind(&Foo::print2, _1, 10)); // must use bind

// 2. mem_fun_ref is for obj.
vector fv;
fv.push_back(Foo());
fv.push_back(Foo());
fv.push_back(Foo());
fv.push_back(Foo());

for_each(fv.begin(), fv.end(), mem_fun_ref(&Foo::print));
cr;

// mem_fun 不能用来绑定对象.
// for_each(fv.begin(), fv.end(), mem_fun(&Foo::print)); // error.

for_each(fv.begin(), fv.end(), bind(&Foo::print, _1)); // bind也可以
cr;

// 如果成员函数接受额外参数(不仅仅是对象本身), 那么mem_fun就无能为力了,要用bind
//for_each(fv.begin(), fv.end(), mem_fun_ref(&Foo::print2, 10));

for_each(fv.begin(), fv.end(), bind(&Foo::print2, _1, 10)); // bind可以有很多参数

// 3. mem_fn既可以用于指针、引用,还可以用于对象本身,因此在C++11中使用mem_fn可以替代mem_fun和mem_fun_ref.
for_each(fpv.begin(), fpv.end(), mem_fn(&Foo::print)); // ptr
for_each(fv.begin(), fv.end(), mem_fn(&Foo::print)); // obj

//for_each(fv.begin(), fv.end(), mem_fn(&Foo::print2, 10));
for_each(fv.begin(), fv.end(), bind(&Foo::print2, _1, 10)); //must use bind

Foo foo;
Foo &foo_ref = foo;
mem_fn(&Foo::print)(foo_ref); // ref

// clear pointers.
for_each(fpv.begin(), fpv.end(), [&](Foo* foo)
{
delete foo;
foo = nullptr;
});

总结
函数 作用
mem_fun 把成员函数转为函数对象,使用对象指针进行绑定
mem_fun_ref 把成员函数转为函数对象,使用对象(引用)进行绑定
mem_fn 把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
bind 包括但不限于mem_fn的功能,更为通用的解决方案,详见目录里std::bind的文章

c++ 11 std::thread 线程 获取线程ID的整数值

https://en.cppreference.com/w/cpp/thread/thread/get_id

std::ostringstream oss;
oss << std::this_thread::get_id();
std::string stid = oss.str();
unsigned long long tid = std::stoull(stid);

std::thread::id tid = std::this_thread::get_id();
_Thrd_t t = (_Thrd_t)(char*)&tid ;
unsiged int nId = t._Id

C++11中的std::call_once

某些场景下,我们需要代码只被执行一次,比如单例类的初始化,考虑到多线程安全,需要进行加锁控制。C++11中提供的call_once可以很好的满足这种需求,使用又非常简单。

头文件#include

    template <class Fn, class... Args>

    void call_once (once_flag& flag, Fn&& fn, Args&&...args);

    第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表。

    call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。对于所有调用函数fn的并发线程,数据可见性都是同步的(一致的)。

    如果活动线程在执行fn时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行fn,依此类推。一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。(实际上once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程)。

    static std::once_flag oc;  // 用于call_once的局部静态变量

    Singleton* Singleton::m_instance;

    Singleton* Singleton::getInstance() {

        std::call_once(oc, [&] () { m_instance = newSingleton(); });

        return m_instance;

    }

    还有一个要注意的地方是 once_flag的生命周期,它必须要比使用它的线程的生命周期要长。所以通常定义成全局变量比较好

带返回值的后台任务 std::async

在这里插入图片描述
std::async()带参数
struct X
{
void foo(int,std::string const&);
std::string bar(std::string const&);
};
X x;
auto f1=std::async(&X::foo,&x,42,“hello”); // 调用p->foo(42, “hello”),p是指向x的指针
auto f2=std::async(&X::bar,x,“goodbye”); // 调用tmpx.bar(“goodbye”), tmpx是x的拷贝副本

C++11线程间同步-可接受超时的函数

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值