c++多线程编程

同一个进程中执行多个线程,由于线程是轻量级的进程,不独立占有资源,即同一个进程中的多个线程共享相同的内存。由于操作系统没有提供保护机制,故而在多线程共享数据时,需要程序员做更多的工作来保证数据正确读取,同时避免锁死。

  • c++11新标准中引入了四个头文件来支持多线程编程:
    • < atomic>
    • < thread>
    • < mutex>
    • < condition_variable>

一、<atomic>解析

所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。

  • 在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。

  • 实例

      atomic_long total(0);       // 用原子数据类型作为共享资源的数据类型
    
  • 通用接口

    • store():存数据
    • load():取数据

二、<thread>解析

主要包含了对线程的管理类std::thread

1. 创建线程实例

std::thread my_thread(func);        //创建实例后,马上开始执行线程中的程序

线程创建后,一定要有join()或depatch()表示线程的类型

2. join()

  • 使用后,原始线程会堵塞,等待新线程执行结束之后,再去销毁线程对象,一般用在线程析构前。

3. detach()

  • 原始线程和新线程分离,新线程不会堵塞原始线程的运行,

4. join()和detach()的使用场合

  • 在一个线程中,开了另一个线程去干另一件事,使用join函数后,原始线程会等待新线程执行结束之后,再去销毁线程对象。
  • 这样有什么好处?---->因为它要等到新线程执行完,再销毁,线程对象,这样如果新线程使用了共享变量,等到新线程执行完再销毁这个线程对象,不会产生异常。如果不使用join,使用detch,那么新线程就会与原线程分离,如果原线程先执行完毕,销毁线程对象及局部变量,并且新线程有共享变量或引用之类,这样新线程可能使用的变量,就变成未定义,产生异常或不可预测的错误。
  • 所以,当你确定程序没有使用共享变量或引用之类的话,可以使用detch函数,分离线程。
  • 但是使用join可能会造成性能损失,因为原始线程要等待新线程的完成,所以有些情况(前提是你知道这种情况,如上)使用detch会更好。

三、<condition_variable>解析

条件变量可以实现线程同步,线程同步是指线程间需要按照预定的先后次序顺序进行的行为。
条件变量可以履行发送者或接受者的角色,作为发送者,它可以通知一个或多个接受者。在这样的工作流程中,接受者正在等待发送者的通知,如果接受者收到通知,它将继续工作。

1. 使用

  • 条件变量提供了两类操作:wait和notify。这两类操作构成了多线程同步的基础。

2. wait()

  • wait是线程的等待动作,直到其它线程将其唤醒后,才会继续往下执行。

      std::mutex mutex;
      std::condition_variable cv;
      // 条件变量与临界区有关,用来获取和释放一个锁,因此通常会和mutex联用。
      std::unique_lock lock(mutex);
      // 此处会释放lock,然后在cv上等待,直到其它线程通过cv.notify_xxx来唤醒当前线程,cv被唤醒后会再次对lock进行上锁,然后wait函数才会返回。
      // wait返回后可以安全的使用mutex保护的临界区内的数据。此时mutex仍为上锁状态
      cv.wait(lock)
    
  • cond.wait()两种形式:

    • cond.wait(locker)
    • cond.wati(locker, lambdafunc)
      说明:第二个参数,是检查条件,使用lambda函数最简单,如果这个函数返回的是true,wait()函数不会堵塞直接返回,如果这个函数返回的是false,wait()函数就会堵塞等待着唤醒(防止被伪唤醒)

3. notify_one()

  • notify就简单多了:唤醒wait在该条件变量上的线程。notify有两个版本:notify_one和notify_all。
    • notify_one 唤醒等待的一个线程,注意只唤醒一个(其中任意一个)。
    • notify_all 唤醒所有等待的线程。使用该函数时应避免出现惊群效应。
      std::mutex mutex;
      std::condition_variable cv;

使用方法:

std::unique_lock lock(mutex);
cv.notify_all(lock)   // 所有等待在cv变量上的线程都会被唤醒。但直到lock释放了mutex,被唤醒的线程才会从wait返回。

四、<mutex>解析

互斥算法避免多个线程同时访问共享资源,并提供线程间的同步支持。
通过设置一个互斥量,在一个线程中对它进行上锁,则在未解锁前,其他任何一个线程遇到该互斥量时都会堵塞,直到原线程对它进行解锁,其他线程才会被响应

1. std::mutex 的成员函数

  • 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
  • lock(),调用线程将锁住该互斥量。
  • unlock(), 解锁,释放对互斥量的所有权。
  • try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则返回fasle,且当前线程也不会被阻塞。

2. Lock类(两种)

锁管理器在构造函数中自动绑定它的互斥体,并在析构函数中释放它。这大大减少了死锁的风险。
unqiue_lock和lock_guard类似功能,但前者有一些函数,使用时可以更灵活

  • lock_guard

      {
      std::mutex m;
      std::lock_guard<std::mutex> lockGuard(m);
      sharedVariable= getVar();
      }             //lockguard只在大括号内的声明周期有效
    
  • unique_lock

      std::unique_lock<std::mutex> lck (mtx);
    

五、其他补充

1 . 类成员函数作为thread的线程函数

  • 两种解决方法,一种是将成员函数写成静态,另一种是thread构造实例时返回两个参数,其中第二个参数是this指针(指向该实例)

      std::thread a(std::bind(&Backend::BackendLoop, this));
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值