IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++多线程 -> 正文阅读

[C++知识库]C++多线程

这篇文章写于2021/08/05

主要用来记录一下C++多线程的知识,包括基础理论部分,以及std中的thread以及mutex等使用方法。

首先是基础知识

1、什么是进程,什么是线程?

  • 进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发。
  • 线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发,线程是操作系统可识别的最小执行和调度单位

2、进程间的通信方式

  • 进程间通信主要包括管道系统IPC(消息队列、信号量、信号、共享内存)套接字socket
  • 管道
    • 普通管道PIPE
      • 它是半双工的 (数据只能在一个方向上流动),具有固定的读端和写端
      • 它只能用于具有亲缘关系的进程之间通信,也就是父子进程或者兄弟进程
      • 它可以看成是一种特殊的文件,读写可以用read、write等函数
    • 命名管道FIFO
      • 它可以在无关的进程之间交换数据
      • 它有路径名与之关联,以一种特殊设备文件的形式存在于文件统中
  • 系统IPC
    • 消息队列
      • 消息队列是消息的连接表,存放在内核中。一个消息队列由一个标识符来标记。具有写权限的进程可以按照一定的规则向消息队列中添加新信息。对消息队列有读权限的进程则可以从消息队列中读取信息。
      • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
      • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容不会删除
      • 消息队列额可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
    • 信号量 Semaphore
      • 信号量与已经介绍过的IPC结构不同,它是一个计数器,可以用来控制多个进程对共享数据的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据
      • 信号量用于进程间同步,若要在进程间传递数据,需要结合共享内存
      • 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
      • 每次对信号量的PV操作不仅限于对信号量值加1减1,而且可以加减任意正整数
      • 支持信号量组
    • 信号 Signal
      • 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
    • 共享内存 Shared memory
      • 它使得多个进程可以访问同一块内存空间,不同进程可以及时的看到对方进程中对共享内存的数据进行更新。这种方式需要依赖某种同步操作,如互斥锁跟信号量等
      • 共享内存是最快的一种IPC,因为进程是直接对内存进行读取
      • 因为多个进程可以同时操作,所以需要进行同步
      • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
  • 套接字
    • socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信

3、线程间的通信方式

  • 临界区
    • 通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问
  • 互斥量Synchronized/Lock
    • 采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可保证公共资源不会被多个线程同时访问
  • 信号量 Semphare
    • 为控制具有有限数量的用户资源设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数量
  • 事件/信号 Wait/Notify
    • 通过通知操作的方式来保持多线程同步,还饿可以方便的实现多线程优先级的操作

4、单核机器上写多线程程序,是否需要考虑加锁,为什么?

  • 需要,只要是多线程程序,就会需要同步和通信,这跟是否单核多核没关系
  • 单核和多核都能实现多线程,单核主要靠轮换来并发,多核可以做到真正意义上的并发

5、死锁发生的条件以及如何避免死锁?

  • 死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的相互等待的现象。死锁发生条件如下
    • 互斥条件
      • 进程对所分配到的资源 不允许其他进程访问,若其他进程访问该资源,只能等待,直到占有该资源的进程使用完后释放该资源
    • 请求和保持条件
      • 进程获得一定资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源
    • 不可剥夺条件
      • 进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放
    • 环路等待条件
      • 进程发生死锁后,必然存在一个进程-资源之间的环形链
  • 解决死锁的办法即破坏上述四个条件之一,主要方法如下
    • 资源一次性分配
      • 从而剥夺请求和保持条件
    • 可剥夺资源
      • 当进程新的资源未得到满足时,释放已占有资源,从而破坏不可剥夺的条件
    • 资源有序分配法
      • 给所有资源赋予序号,进程按照编号递增请求资源,释放则相反,从而破坏环路等待条件

6、父进程退出时,子进程会如何?父线程退出时,子线程会如何?

  • 父进程退出时,子进程会过继给systemd
  • 父线程退出时,子线程不一定退出,只有父进程退出时,子线程才退出
std中的thread相关
  • 创造一个线程并令该线程执行f函数 std::thread t(f, arg1, arg2, ...);
  • 令父线程先停止做事,让子线程先做 t.join();
  • 分离线程,让该线程一直做下去,直到进程结束 t.detach();
线程间共享数据 (主要是锁)
  • 定义申明一个互斥锁 std::mutex mtx;
  • 锁上一个能自动解锁的互斥锁 std::lock_guard<std::mutex> lock(mtx);
  • 能够同时给多个互斥锁上锁 std::lock(mtx1, mtx2);
  • 获取能自动解锁的互斥锁权限 std::lock_guard<std::mutex> lock(mtx, std::adopt_lock);
  • 申明一个层次锁,每次只能获取更低编号的 hierarchical_mutex high_level_mutex(10000);
  • 让互斥锁保持未上锁状态 std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
  • 类似于读写锁 std::shared_mutex shared_mtx;
  • 提供多线程读取 std::shared_lock<std::shared_mutex> shared_lock(shared_mtx);
  • 提供独占写访问,此时读线程也被阻塞 std::lock_guard<std::shard_mutex> lock(shared_mtx);
  • unique_locklock_guard区别?
    • unique_locklock_guard更具有弹性,unique_lock不一定要拥有mutex。可以创建出空的对象,unique_lock可以转移,可以函数回传。unique_lock提供lockunlocktry_lock成员函数。
同步并发操作 (条件变量)
  • 等待线程在检查的时候休眠 std::this_thread::sleep_for(std::chrono::milliseconds(100));
  • 条件变量的创建和使用
    • 头文件 #include <condition_variable>
    • 创建一个条件变量 std::condition_variable data_cond data_cond;
    • 通知等待线程 data_cond.notify_one();
    • 当执行到此或者被notify的时候触发。如果有第二参数,先判断第二参数return的值,如果是false,则开锁并阻塞直到被notify重新验证;如果是true,关锁继续执行语句。当没有第二参数时候,执行到此等同于第二参数为false,被notify则直接等同于第二参数为true data_cond.wait(u_lock, []{ return cond; });
  • 生产者消费者例子
#include <iostream>
#include <condition_variable>
#include <thread>
#include <mutex>
#include <queue>

std::mutex mtx;
std::queue<int> q;
std::condition_variable cond;

int a = 1;

void preparation() {
    for (int i = 0; i < 20000; ++i) {
        {
            // std::this_thread::sleep_for(std::chrono::milliseconds(1));
            std::lock_guard<std::mutex> g_lock(mtx);
            q.push(i);
            std::cout << i << " has been prepared." << std::endl;
        }
        cond.notify_one();
    }

}

void processing() {
    while (true) {
        std::unique_lock<std::mutex> u_lock(mtx);
        cond.wait(u_lock, []{ return !q.empty(); });
        int front = q.front();
        std::cout << front << " has been processing." << std::endl;
        q.pop();
        u_lock.unlock();
        // std::this_thread::sleep_for(std::chrono::milliseconds(1));
        if (front == 19999) break;
    }
}

int main() {
    std::thread t1(preparation);
    std::thread t2(processing);
    t1.join();
    t2.join();
    return 0;
}

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-06 21:09:22  更:2021-08-06 21:09:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/9 23:53:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码