C++线程库

C++线程库_第1张图片

文章目录

  • 1. 线程库
    • 1.1 thread类的简单介绍
    • 1.2 mutex的种类
    • 1.3 原子性操作库(atomic)
    • 1.4 lock_guard与unique_lock
    • 1.5 condition_variable

1. 线程库

1.1 thread类的简单介绍

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

thread() 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
thread(fn,args1, args2,…) 构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
get_id() 获取线程id
jionable() 线程是否还在执行,joinable代表的是一个正在执行中的线程。
jion() 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach() 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

C++线程库_第2张图片
当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体:
C++线程库_第3张图片
C++线程库_第4张图片
举个例子:
C++线程库_第5张图片
thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。

可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
C++线程库_第6张图片
线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参。因为其实际引用的是线程栈中的拷贝,而不是外部实参
C++线程库_第7张图片
如果想要通过形参改变外部实参时,必须借助std::ref()函数,强制左值引用传参。
C++线程库_第8张图片
注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数

1.2 mutex的种类

在C++11中,Mutex总共包了四个互斥量的种类:
1.std::mutex
C++11提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。
mutex最常用的三个函数:
C++线程库_第9张图片
C++线程库_第10张图片
2. std::recursive_mutex
其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock()。除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

3. std::timed_mutex
比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。
C++线程库_第11张图片
4. std::recursive_timed_mutex

1.3 原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。

我们来看这里有一个问题:
C++线程库_第12张图片
C++线程库_第13张图片
这两种加锁谁更高效一点呢
第一种是并行加加,线程1加锁sum++之后,线程2紧跟着就加锁sum++。之后又到线程1了

第二种是串行加加,线程1加完,线程2再加

这里线程间来回切换加上频繁的加锁解锁的消耗,第一种比第二种要耗时一点。但是第一种的粒度比第二种的更细一点。因此C++11中引入了原子操作。

所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。
C++线程库_第14张图片
注意:需要使用以上原子操作变量时,必须添加头文件< atomic >。
C++线程库_第15张图片
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。程序员可以使用atomic类模板,定义出需要的任意原子类型。
在这里插入图片描述
在这里插入图片描述
在多线程环境下,如果想要保证某个变量的安全性,只要将其设置成对应的原子类型即可,即高效又不容易出现死锁问题。但是有些情况下,我们可能需要保证一段代码的安全性,那么就只能通过锁的方式来进行控制。

1.4 lock_guard与unique_lock

但是锁控制不好时,可能会造成死锁,最常见的比如在锁中间代码返回,或者在锁的范围内抛异常。因此:C++11采用RAII的方式对锁进行了封装,即lock_guard和unique_lock。

std::lock_gurad 是 C++11 中定义的模板类。定义如下:
C++线程库_第16张图片
通过上述代码可以看到,lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。

lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock
C++线程库_第17张图片
与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:
C++线程库_第18张图片

1.5 condition_variable

condition_variable主要用来进行线程之间的互相通知。condition_variable和Linux posix的条件变量并没有什么大的区别,主要还是面向对象实现的。条件变量的文档

支持两个线程交替打印,一个打印奇数,一个打印偶数。
使用条件变量要包含头文件:#include < condition_variable >
C++线程库_第19张图片

你可能感兴趣的:(C++,c++,开发语言,线程库)