点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达

本文整理自:知乎用户知乎专栏

Part 1. 基本概念

1.1 进程

1.2 线程

1.3 线程与线程的区别联系

除了上面概念中所提到的,还有:

在一个应用程序(进程)中同时执行多个小的部分(线程),这就是多线程。多个线程虽然共享一样的数据,但是却执行不同的任务。

1.4 并发

并发是指在同一个时间里CPU同时执行两条或多条命令。单核CPU和C++11以前实现的并发一般是伪并发。但随着多核CPU的普及,C++11开始支持真正意义上的并发。

C++11可以通过多线程实现并发,这是一种比较底层、传统的实现方式。C++11引入了5个头文件来支持多线程编程:////

#include    // C++11 原子操作,限制并发程序对共享数据的使用,避免数据竞争#include    // 该头文件主要声明了std::thread类,另外std::this_thread命名空间也在该头文件中#include     // C++11 互斥量Mutex。在多线程环境中,有多个线程竞争同一个公共资源,就很容易引发线程安全的问题#include   // C++11 并发编程 主要包含了与条件变量相关的类和函数

Part 2.std::mutex互斥访问

是C++标准程序库中的一个头文件,定义了C++11标准中一些互斥访问的类与方法。

其中std::mutex表示普通互斥锁,可以与std::配合使用,把std::mutex放到中时,mutex会自动上锁,析构时,同时把mutex解锁。因此std::mutex可以保护同时被多个线程访问的共享数据,并且它独占对象所有权,不支持对对象递归上锁。

可以这样理解:各个线程在对共享资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。(下图来自网络)

c线程_c++线程编程_c 多线程编程

常用的成员函数有:

构造函数:std::mutex不支持copy和move操作,最初的mutex对象处于状态。

lock函数:互斥锁被锁定。如果线程申请该互斥锁,但未能获得该互斥锁,则申请调用的线程将阻塞(block)在该互斥锁上;如果成功获得该互诉锁,则该线程一直拥有互斥锁直到调用解锁;如果该互斥锁已经被当前调用线程锁住,则产生死锁()。

函数:互斥锁解锁,释放调用线程对该互斥锁的所有权。

一个简单的例子:

std::mutex mtx;  // 创建一个互斥锁static void print_(int n, char c){  mtx.lock();       // 申请对访问资源,上锁  for (int i = 0; i<n; ++i) { std::cout << c; }  std::cout << 'n';  mtx.unlock();    // 对资源操作结束,解锁释放互斥锁的所有权}

Part 3.std:: 锁管理模板类

std::为锁管理模板类,是对通用mutex的封装。

std::对象以独占所有权的方式( )管理mutex对象的上锁和解锁操作,即在对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而的生命周期结束之后,它所管理的锁对象会被解锁。因此用管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。

在使用条件变量std::时需要使用std::而不能使用std::。

其常用的成员函数为:

构造函数:禁止拷贝构造,允许移动构造;

lock函数:调用所管理的mutex对象的lock函数;

函数:调用所管理的mutex对象的函数;

例如这样使用:

std::mutex mtx;   // 定义一个互斥锁 void print_thread_id(int id) {     std::unique_lock lck(mtx, std::defer_lock);  // 定义一个锁管理对象(参数2其实可以省略)     lck.lock();     std::cout << "thread #" << id << 'n';     lck.unlock();}

Part 4. std:: 条件变量

是C++标准程序库中的一个头文件,定义了C++11标准中的一些用于并发编程时表示条件变量的类与方法等。

条件变量的引入是为了作为并发程序设计中的一种控制结构。当多个线程访问同一共享资源时,不但需要用互斥锁实现独享访问以避免并发错误(竞争危害)c 多线程编程,在获得互斥锁进入临界区后还需要检验特定条件是否成立:

若不满足该条件,拥有互斥锁的线程应该释放该互斥锁,使用函数把自身阻塞(block)并挂到条件变量的线程队列中

若满足该条件,拥有互斥锁的线程在临界区内访问共享资源,在退出临界区时通知()在条件变量的线程队列中处于阻塞状态的线程,被通知的线程必须重新申请对该互斥锁加锁。

条件变量std::用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程。std::需要与std::配合使用。

常用成员函数:

(1)构造函数:仅支持默认构造函数。

(2)wait():当前线程调用wait()后将被阻塞,直到另外某个线程调用*唤醒当前线程。当线程被阻塞时,该函数会自动调用std::mutex的()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(c 多线程编程,通常是另外某个线程调用*唤醒当前线程),wait()函数自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种:

无条件被阻塞:调用该函数之前,当前线程应该已经对 lck完成了加锁。所有使用同一个条件变量的线程必须在wait函数中使用同一个。该wait函数内部会自动调用lck.()对互斥锁解锁,使得其他被阻塞在互斥锁上的线程恢复执行。使用本函数被阻塞的当前线程在获得通知(,通过别的线程调用 *系列的函数)而被唤醒后,wait()函数恢复执行并自动调用lck.lock()对互斥锁加锁。

带条件的被阻塞:wait函数设置了谓词(),只有当pred条件为false时调用该wait函数才会阻塞当前线程,并且在收到其它线程的通知后只有当pred为true时才会被解除阻塞。因此,等效于while (!pred()) wait(lck).

(3): 唤醒所有的wait线程,如果当前没有等待线程,则该函数什么也不做。

(4):唤醒某个wait线程,如果当前没有等待线程,则该函数什么也不做;如果同时存在多个等待线程,则唤醒某个线程是不确定的()。

简单的说就是,当std::对象的某个wait函数被调用的时候,它使用std::(通过std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的std::对象上调用了函数来唤醒当前线程。

Part 5. std:: 原子操作

是C++标准程序库中的一个头文件,定义了C++11标准中一些表示线程、并发控制时进行原子操作的类与方法,主要声明了两大类原子对象:std::和std::。

原子操作的主要特点是原子对象的并发访问不存在数据竞争,利用原子对象可实现数据结构的无锁设计。在多线程并发执行时,原子操作是线程不会被打断的执行片段。

(1)类

是一种简单的原子bool类型,只支持两种操作:(flag=true)和clear(flag=false)。

跟std::的其它所有特化类不同,它是锁无关的。

结合std::::()和std::::clear(),std::对象可以当作一个简单的自旋锁(spin lock)使用。

只有默认构造函数,禁用拷贝构造函数,移动构造函数实际上也禁用。

如果在初始化时没有明确使用宏初始化,那么新创建的std::对象的状态是未指定的(),既没有被set也没有被clear;如果使用该宏初始化,该std::对象在创建时处于clear状态。

(2)std::类

std::提供了针对bool类型、整形()和指针类型的特化实现。每个std::模板的实例化和完全特化定义一个原子类型。

若一个线程写入原子对象,同时另一个线程从它读取,则行为良好定义。

原子对象的访问可以按std::所指定建立线程间同步,并排序非原子的内存访问。

std::可以以任何可平凡复制( )的类型T实例化。

std::既不可复制亦不可移动。

(val):可以由构造函数直接执行此宏初始化std::对象。

std:: 常用的成员函数:

std::::store(val) 函数将参数 val 复制给原子对象所封装的值。

std::::load() 读取被原子对象封装的值。

std::::(val) 读取并修改被封装的值, 会将 val 指定的值替换掉之前该原子对象封装的值,并返回之前该原子对象封装的值,整个过程是原子的.

() 默认构造函数,由默认构造函数创建的 std:: 对象处于未初始化()状态,对处于未初始化()状态 std::对象可以由 函数进行初始化。

(T val) 初始化构造函数,由类型 T初始化一个 std::对象。

(const &) 拷贝构造函数被禁用。

参考文献:

/

/

/

/

/-li/

/


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注