c++_learning-并发与多线程

news/2024/5/17 18:46:57/文章来源:https://blog.csdn.net/qq_44599368/article/details/133934896

并发与多线程

  • 并发:
  • 进程:
  • 线程:
    • 基本概念:
    • 线程安全:
      • 问题出现的场景:
      • 涉及的性质:
      • 如何保证线程安全?
  • 并发的实现手段(优先使用多线程并发):
    • 多进程并发中,进程之间的通信IPC:
    • 多线程并发,单进程中创建多个线程来实现并发:
  • 线程的启动、结束和创建多线程的方法:
    • 创建线程的要点:
      • 给子线程入口函数传递参数时,要用值传递(不能用引用或者指针):
      • 传递类对象、智能指针、类成员函数指针作为线程参数:
    • 创建和等待多个线程:
      • 将多个线程对象放入thread数组:
      • 将多个线程对象放入容器:
  • 互斥锁:
    • 多个线程的数据共享:
      • 数据共享问题分析:
      • 解决的方法:引入互斥量mutex,
      • c++11中提供四种互斥锁:
        • mutex互斥锁:
        • timed_mutex带超时机制的互斥锁:
        • recursive_mutex递归互斥锁:
        • recursive_timed_mutex带超时机制的递归互斥锁
      • 互斥量mutex:
        • 互斥量可以理解为一把锁:
        • mutex类:
      • C++中的死锁:
      • `unique_lock`和`lock_guard`都是管理锁的辅助类:
        • 为防止mutex加锁后忘记unlock(),引出std::lock_guard()类模板:
        • 类模板unique_lock取代lock_guard:
          • unique_lock和lock_guard的**异同点:**
          • `unique_lock<std::mutex>` 的**第二个参数**:
          • unique_lock的成员函数:
          • unique_lock**所有权的转移**:
      • 线程的移动和交换
    • 单例设计模式共享数据的问题:
      • 懒汉模式:
      • “饿汉模式”线程安全:
    • 条件变量:std::condition_variable、wait()、notify_one():
      • 条件变量是一种线程同步机制:
      • 常使用条件变量的场景:生产-消费者模型(高速缓存队列):
      • c++11的`#include<condition_variable>`条件变量提供了两个类:
        • `condition_variable`:
          • 成员函数:
          • 当另一个线程的notify_one()函数将本线程中wait()函数激活后,
        • `condition_variable_any`:
  • std::async、std::future:创建后台任务并返回值:
  • std::native_handle()函数:
  • 原子类型`#include<atomic>`:
    • 原子操作介绍:
    • 成员函数:
    • 使用细节:

并发:

一个程序(单核CPU)通过操作系统的调度进行“任务切换”,从而同时执行多个独立的任务,即提高性能,可以同时干多个事,但切换需要时间的开销。

进程:

就是一个可执行程序运行起来了 windows(双击.exe文件)/linux(./文件名)。一个进程中有一个主线程,用来执行main函数中的代码。

线程:

基本概念:

  • 除了主线程之外,可以通过写代码自己创建线程,每创建一个线程就多干一件事。
  • 切换需要耗费时间的成本,所以并不是线程越多越好。
  • 每个线程都需要一个独立的堆栈空间(1M),线程之间的切换需要保存很多中间状态。线程结束后,需要回收该线程的这些资源。

线程安全:

问题出现的场景:

  • 同一个进程中的多个线程共享该进程的全部系统资源。
  • 多个线程访问同一个共享资源时,会产生冲突。

涉及的性质:

顺序性:顺序执行代码。

  • 存在的问题:为了提高程序运行效率,CPU可能会对代码进行优化,按更高效的顺序执行代码,而不一定是顺序执行,但执行结果与顺序执行的结果是一致的。

可见性:当多个线程并发访问共享变量时,一个线程对共享变量的修改,其他线程能够立即看到。

  • 存在的问题:线程操作共享变量时,会将该变量从内存加载到CPU的缓存中,修改该变量后CPU会立即更新缓存,但不一定会立即将它写回内存中。此时,其他线程访问该变量,是从内存中得到的旧数据,而非第一个线程操作后的数据。

原子性:一个操作(可能包含多个步骤)要么全部执行,要么全部不执行。

  • 存在的问题:CPU执行“读取指令、读取内存、执行指令、写回内存”的过程中,并不是一气呵成的,可能会被打断。

如何保证线程安全?

  1. volatile关键字:保证内存变量的可见性禁止代码优化(重排序)
  2. 原子操作(原子类型)。
  3. 线程同步(互斥锁)。

并发的实现手段(优先使用多线程并发):

多进程并发中,进程之间的通信IPC:

  1. 同一台电脑:管道(有名管道、无名管道)、文件、消息队列、共享内存。
  2. 不同的电脑:socket通信技术,即本地套接字。

多线程并发,单进程中创建多个线程来实现并发:

  • 每一个进程中的所有线程共享地址空间(共享内存)(全局变量、指针、引用,都可以在线程之间传递;且开销比较小)。
  • 共享内存带来的问题:数据一致性的问题。

线程的启动、结束和创建多线程的方法:

  • 主线程是从main()函数开始执行的;子线程的执行也必须从一个函数开始;

  • 当一个进程中,主线程执行结束后,如果子线程还没有结束会被操作系统强制终止。如果要保证子线程的运行状态,就必须保证主线程的运行状态,或者使用detach使子线程处于“分离状态”。

  • c++11标准线程库#include<thread>,提高了移植性。其中,thread是一个标准库中的类,使用时要创建类对象。

    // 使用时,只是引用了地址并未发生拷贝动作
    std::ref()   // 给线程函数传递参数时,可通过此函数避免发生拷贝动作/* 构造函数 */
    // 1.默认构造函数,构造一个线程对象且不执行任何任务:
    thread() noexcept;// 2.创建线程对象,并在线程中执行任务函数
    template<class Function, class... Args>
    explicit thread(Function&& fx, Args&&... args);    // fx是任务函数,args是任务函数执行时需要的参数
    // 任务函数可以是普通函数、类的静态成员函数、类的非静态成员函数、lambda表达式、仿函数// 3.删除了拷贝构造函数,不允许线程对象之间的拷贝
    thread(const thread&)=delete// 4.移动构造函数,将线程other的资源所有权转移给新创建的线程对象
    thread(thread&& other) noexcept/* 赋值函数 */
    // 左值的other禁止拷贝,故删除该赋值函数
    thread& operator=(const thread& other)=delete
    // 右值的other能赋值,会发生资源所有权的转移
    thread& operator=(const thread&& other) noexcept/* 阻塞主线程,并等待子线程执行完毕后回收它的资源,然后子线程与主线程汇合一起执行其他程序 */ 
    join() 
    /* 主线程不用等待子线程结束;一旦detach后,该子线程会被c++运行时库接管,运行结束后,由运行时库负责清理该线程相关的资源 */
    detach()/* 判断子线程的分离状态并返回布尔类型,即是否可以成功使用join()、detach()(true则是可以使用) */
    joinable()/* c++11中,命名空间std::this_thread的全局函数 */
    this_thread::get_id()          // 得到线程的id
    this_thread::sleep_for()       // 使线程休眠一段时间// 线程休眠1秒Sleep(1000);this_thread::sleep_for(chrono::seconds(1));
    this_thread::sleep_until()     // 让线程休眠到某个时间点,可实现线程定时任务
    this_thread::yield()           // 让线程主动让出自己已抢到的CPU时间片/* thread类的其他成员函数 */
    swap(std::thread& other)                           // 交换两个线程对象
    static unsigned hardware_concurrency() noexcept    // 返回硬件线程上下文的数量
    
  • 其他创建线程的方法:

    用类:类中必须要重载()运算符,将该类对象变成可调用对象,才能用来初始化线程对象。其中涉及到的过程:有参构造 --> 拷贝构造函数 --> 析构函数,最后对有参构造的类对象进行析构。

    用lambda表达式,完成创建。

    auto mylambdathread = []() {cout << "子线程开始执行" << endl;// .....cout << "子线程执行结束" << endl;
    };
    

创建线程的要点:

给子线程入口函数传递参数时,要用值传递(不能用引用或者指针):

  1. 简单的类型参数要用,值传递,不要用引用。

  2. 如果传递类对象,则要避免隐式类型转换(即创建子线程时,就生成临时对象并拷贝给入口函数)且该入口函数的形参中类对象要用常量引用。

    // 该入口函数的形参中,类对象用常量引用
    myPrint(const int i, const string &mystring) {...}// 创建子线程时,就生成临时对象并拷贝给入口函数
    thread mythreadObj(myPrint, i, string(mychar));
    
    • 整个过程发生在主线程中;
    • 会调用该类的(有参)构造函数和拷贝构造函数,和一个析构函数(析构的是(有参)构造函数);
    • 临时对象构造时机的捕获:通过std::this_thread:;get_id()得到,给子线程入口函数传递类对象时,直接进行类型转换,这会将产生的临时对象会拷贝给入口函数(整个过程都发生在主线程中)。

只使用.join(),就不会存在局部变量失效,导致线程对内存的非法引用的问题。

传递类对象、智能指针、类成员函数指针作为线程参数:

  1. 传递类对象时,如果希望在子线程中修改该类对象的成员,则要用std::ref()函数引用类对象的地址且不会发生拷贝动作。

  2. 智能指针unique_ptr作为线程参数:

    unique_ptr<int> i_uptr(new int(100));thread mythreadObj(myPrint, std::move(i_uptr));
    
  3. 类成员函数指针:

    thread mythreadObj(&A::func, a, 10);thread mythreadObj(&A::func, &a, 10);   // &a == std::ref(a);
    

创建和等待多个线程:

将多个线程对象放入thread数组:

// 用thread数组创建多个线程
thread mythreadObj[10];
for (int i = 0; i < sizeof(mythreadObj) / sizeof(mythreadObj[0]); i++)
{mythreadObj[i] = thread(myPrint1, i);
}
for (int i = 0; i < sizeof(mythreadObj) / sizeof(mythreadObj[0]); i++)
{mythreadObj[i].join();
}

将多个线程对象放入容器:

// 用vector容器创建多个线程
vector<thread> threadVctor;
// 创建10个线程,线程入口函数统一使用myPrint()
for (int i = 0; i < 10; i++)
{threadVctor.push_back(thread(myPrint1, i));  // 创建thread对象,并发生了拷贝到了容器中
}
for (vector<thread>::iterator iter = threadVctor.begin(); iter != threadVctor.end(); iter++)
{(*iter).join();   // 等价于iter->join();
}

互斥锁:

c++11-14-17_内存管理(RAII)_多线程:详细分析“c++多线程从原理到线程池实现”。

多个线程的数据共享:

数据共享问题分析:

  • 1)只读的数据:是安全稳定的,不需要特殊的处理;
  • 2)有读有写:读的时候不能写,写的时候不能读;否则会发生混乱而报错;

解决的方法:引入互斥量mutex,

  • 写的时候锁住不让它读;读的时候锁住不让它写;确保同一时间只能有一个线程操作该共享资源;
  • 某个线程把共享数据锁住、操作数据、解锁;其他操作共享数据的线程必须等待解锁,然后才能锁定、操作数据、解锁;

c++11中提供四种互斥锁:

mutex互斥锁:

含有的成员函数:.lock().unlock().join().joinable().detach()

timed_mutex带超时机制的互斥锁:

含有的成员函数:.try_lock_for(时间长度).try_lock_until(时间点)

recursive_mutex递归互斥锁:

允许同一个线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

recursive_timed_mutex带超时机制的递归互斥锁

互斥量mutex:

互斥量可以理解为一把锁:
  • lock的代码段越少,执行的速度越快,整个程序的运行效率越高。

    lock的力度,需要掌控。力度越大,执行效率越低;力度越小,共享数据的能保护力度越低。

  • 线程持有锁的时间越长,程序运行效率越低。

mutex类:
.lock()        // 加锁
/*互斥锁有锁定、未锁定两种状态:1)未锁定状态下,调用lock()的线程会得到互斥锁的所有权,并将其上锁;2)锁定状态下,调用lock()的线程会“阻塞等待”,直到互斥锁变成未锁定的状态;多个线程尝试用lock()成员函数进行加锁,且只有一个线程能加锁成功;没锁成功,则子线程会卡在这并不断尝试去锁这把锁头。
*/.unlock()     // 解锁:只有持有锁的线程才能解锁
/* std::mutex::lock()、std::mutex::unlock(),必须成对使用 */.try_lock()   // 尝试加锁
/*1)如果互斥锁是为未加锁的状态,则加锁成功,函数返回true2)如果互斥锁是锁定的状态,则加锁失败,函数立即返回false且“线程不会阻塞”
*/

C++中的死锁:

产生的条件:至少要有两把锁头,也就是两个互斥量才能产生。

死锁的现象:当两个线程(都对两把锁进行锁和解锁的操作,锁的顺序相反)分别锁住了两个锁头,就会各自去寻找另一个锁头,从而产生了死锁。

本质原因:两个线程上锁的顺序不同。

死锁的解决方案:

  • 保证两个互斥量在两个线程中的,锁的顺序相同。

  • std::lock()函数模板:

    // 作用:一次能够锁住两个或者两个以上的互斥量。
    // 不存在多个线程中,锁的顺序问题导致死锁的问题。// 写法一:
    std::lock(m_mutex1, m_mutex2);
    m_mutex1.unlock(); m_mutex2.unlock();// 写法二:
    std::lock(m_mutex1, m_mutex2);std::lock_guard<std::mutex> lguard1(m_mutex1, std::adopt_lock);
    std::lock_guard<std::mutex> lguard2(m_mutex2, std::adopt_lock);   
    // 参数std::adopt_lock是结构体对象,起标记作用:作用就是表示该互斥量已经被lock(),不需要lock_guard<mutex>再进行锁的操作,只需进行解锁
    

    std::lock():

    1)如果有一个互斥量没锁住,它就会在那里等,等待所有互斥量都被锁住之后,才能往下走

    2)要么两个互斥量都锁住了,要么都没锁住;如果只有一个锁住了,另一个没锁成功,则会解锁已经被锁住的锁

  • 建议一个线程中不要对两个互斥量同时上锁,最好一个一个锁;工作中,使用lock_guard<mutex>足够了。

unique_locklock_guard都是管理锁的辅助类:

为防止mutex加锁后忘记unlock(),引出std::lock_guard()类模板:
std::mutex m_mutex;
std::lock_guard<std::mutex> lockguard(m_mutex);

lock_guard()采用了RAII的思想:

  • 在其构造函数中,执行std::mutex::lock()
  • 在其析构函数中,执行std::mutex::unlock()

缺点:lock_guard的解锁控制不灵活,但可以通过对lock_guard施加作用域来控制lock、unlock的生命周期。

std::lock_guard<std::mutex> lguard(m_mutex, std::adopt_lock);
// std::adopt_lock是结构体对象,起标记作用:表示该互斥量已经被lock(),不需要lock_guard<mutex>再进行锁的操作,只需进行解锁
类模板unique_lock取代lock_guard:
unique_lock和lock_guard的异同点:
  • 区别:unique_lock占用的内存更多,效率低,但更加灵活。
  • 相同点:都是RAII风格,在构造函数中加锁,在析构函数中解锁。
unique_lock<std::mutex>第二个参数
/* 1)std::adopt_lock是结构体对象,起标记作用:表示该互斥量已经被lock(),不需要unique_guard<mutex>再进行锁的操作,只需进行解锁。*//* 2)std::try_to_lock(使用前不能锁):会尝试用mutex的lock()去锁定这个mutex;但如果没有锁成功,则会立即返回,并不会阻塞在那里。*/
std::unique_lock<std::mutex> lck(mtx, std::try_to_lock);// 判断互斥量是否拿到了锁
bool succ = lck.owns_lock();/* 3)std::defer_lock:初始化未加锁的互斥量;但需要自己解锁。*/
unique_lock<std::mutex> lck(mtx, std::defer_lock);  // 需要自己加锁;
for (int i = 0; i < n; i++)
{lck.lock();//操作共享数据//........lck.unlock();//操作非共享数据//........lck.lock();//操作共享数据//........
}
unique_lock的成员函数:
lock()         // 加锁
unlock()       // 解锁
try_lock()     // 尝试加锁,拿到锁返回true,否则返回false且不会发生阻塞// release():
/* 返回它所管理的mutex对象的指针(原始的mutex指针),并释放所有权(即unique_lock和mutex不再有关系了)*/
unique_lock<std::mutex> lck(mtx);    
mutex* pt_rel = lck.release();            
// mtx所有权转让给pt_rel,因此需要pt_rel自己解锁//pt_rel->lock();    // Error:接收lck.release()无需再次上锁,即已上锁// .............操作共享数据pt_rel->unlock();
unique_lock所有权的转移
  1. 通过std::move()进行所有权转移

    std::unique_lock<std::mutex> lck(mtx); 
    std::unique_lock<std::mutex> lck_move(std::move(lck));   // std::move()移动构造函数使用,转移所有权
    
  2. 通过函数返回局部的unique_lock对象(该局部对象,会生成临时对象并调用unique_lock的移动构造函数);

    std::mutex mtx;
    unique_lock<mutex> umtx_lockfunc()
    {unique_lock<mutex> temp_mtxLock(mtx); // temp_mtxLock拥有mtx的所有权,可将该互斥量mtx的所有权转移给其他的unique_lock对象(但不能复制)return temp_mtxLock;
    }// 通过函数umtx_lockfunc(),返回局部的unique_lock对象,生成了临时对象和调用了移动构造函数,从而将所有权转移给lock_move
    std::unique_lock<std::mutex> lck_move = umtx_lockfunc(); // .......操作共享数据lck_move.unlock();
    

线程的移动和交换

单例设计模式共享数据的问题:

单类模式:确保多线程同时尝试创建一个的单类的实例时,只有一个能创建成功。提供一个访问它的全局访问点,该实例被所有程序模块共享。

懒汉模式:

单例实例在第一次被使用时才进行初始化,称为“延迟初始化”。

  • C++11前,多线程环境下local static对象的初始化并不是线程安全的。具体表现就是:

    1)如果一个线程正在执行local static对象的初始化语句但还没有完成初始化;

    2)此时若其它线程也执行到该语句,那么这个线程会认为自己是第一次执行该语句并进入该local static对象的构造函数中,这会造成这个local static对象的重复构造,进而产生内存泄露问题。

c++11引入std::call_once()的函数模板:保证函数只被调用一次;具备有互斥的能力,且效率高占用的互斥资源少。

#include<mutex>
template<class callable, class...Args>
void call_once(std::once_flag& flag, Function&& fx, Args&&... args); // fx(args...):函数名和参数
// 当call_once调用成功后,once_flag对象会变成已调用的状态,后续就无法再次调用了;本质上once_flag是取值为0、1的锁。	
#include <iostream>
#include <mutex>
#include <memory>
using namespace std;/* C++11线程安全 */
class Singleton
{
public:shared_ptr<Singleton> getInstance(){// 该call_once中的函数或可调用对象一旦执行过一次,initFlag的状态就会改变。// 下次再调用时,会首先检查initFlag的状态,故不会再执行其中的函数或可调用对象。std::call_once(initFlag, [&this]() {singleton = shared_ptr<Singleton>(new Singleton());});return singleton;}
private:static shared_ptr<Singleton> singleton;// std::once_flag对象是一个不可复制、不可移动的对象,但可被默认构造。// 作用:跟踪call_once中,函数或可调用对象的是否已经被执行。static std::once_flag initFlag;Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
}; 
static shared_ptr<Singleton> singleton = nullptr;
static once_flag singletonFlag;  // 调用默认构造函数
  • C++11规定,在一个线程开始local static对象的初始化后到完成初始化前,其他线程执行到这个local static对象的初始化语句就会等待。
/* C++11线程安全 */
class Singleton
{
private:static Singleton* instance;
private:Singleton() {};~Singleton() {};Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
public:static Singleton* getInstance() {if(instance == nullptr) {instance = new Singleton();}return instance;}
};// init static member
Singleton* Singleton::instance = nullptr;

“饿汉模式”线程安全:

#include <iostream>
using namespace std;class Singleton
{
public:// 获取单实例static Singleton* GetInstance(){ return singleton;} // 释放单实例,进程退出时调用static void deleteInstance(){    if (singleton != nullptr){delete singleton;singleton = nullptr;}}
private:// 禁止外部调用默认构造和析构函数Singleton() {}~Singleton() {}// 禁用拷贝构造和拷贝赋值函数Singleton(const Singleton &single);const Singleton &operator=(const Singleton &single);
private:// 唯一单实例对象指针static Singleton* singleton;
};
// 代码一运行就初始化创建实例且创建一次,本身就线程安全
Singleton* Singleton::singleton = new Singleton();int main()
{Singleton::GetInstance();return 0;	
}

条件变量:std::condition_variable、wait()、notify_one():

条件变量是一种线程同步机制:

  • 当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒
  • 举例:在两个线程中,一个线程向另一个线程发送消息,从而激活另一个线程

常使用条件变量的场景:生产-消费者模型(高速缓存队列):

为保护共享资源,“条件变量”需与“互斥锁”结合使用。。

在这里插入图片描述

注:生产者可以是单/多线程,而消费者一般都是多线程,俗称线程池。生产者产生的数据放入缓存队列,需要条件变量来通知消费者。

class A2
{
public: A2() { cout << "默认构造函数" << endl; }virtual ~A2() { cout << "虚析构函数" << endl; }// 将收集到的数据放入队列void InsertMessageQueue(int val){for (int i = 0; i < 100000; i++){std::unique_lock<std::mutex> umtx_lock (m_mutex);MessageQueue.push_back(val);m_conditionVar.notify_one();    // 尝试将另一个线程中的wait()唤醒}}void OutMessageQueue(){while (true)   // 死循环{std::unique_lock<std::mutex> umtx_lock(m_mutex);/* 循环阻塞当前线程,直到通知到达且谓词满足 */m_conditionVar.wait(umtx_lock, [this]() {if (!this->MessageQueue.empty()){	return true;}return false;});// 流程能执行到这里,表明互斥量一直是锁着的状态;MessageQueue中至少有一条命令command int retVal = MessageQueue.pop_back();return retVal;}    }
public:list<int> MessageQueue;std::condition_variable m_conditionVar;std::mutex m_mutex;
};

c++11的#include<condition_variable>条件变量提供了两个类:

condition_variable
成员函数:

只支持与普通mutex搭配,效率更高。

condition_variable()   // 默认构造函数notify_one()           // 通知一个线程的wait函数
notify_all()           // 同时通知多个线程的wait函数/*1)会把互斥锁解开;2)阻塞,并等待“被唤醒”且“满足谓词”;3)给互斥锁加锁;总结:wait导致当前线程阻塞直至“被条件变量通知”或“虚假唤醒发生”,可选地循环直至“满足某谓词”。
*/
wait(unique_lock<mutex> lock)              // 阻塞当前线程,直到通知到达
wait(unique_lock<mutex> lock, Pred pred)   // 循环阻塞当前线程,直到通知到达且谓词满足 

在这里插入图片描述

当另一个线程的notify_one()函数将本线程中wait()函数激活后,

① wait()会尝试不断重新获取互斥量的锁,获取不到会卡在这里;获取到了(等于给互斥量加锁)则会继续执行 ②;

② 如果wait()函数有第二个参数,会判断lambda表达式,为false则解锁互斥量并阻塞在本行;为true则wait()返回,并继续向下执行流程(此时互斥量一直是被锁的状态);如果wait()函数没有第二个参数,则wait()返回并继续向下执行流程;

condition_variable_any

一种通用的条件变量,可以与任意的mutex搭配使用,包括自定义的锁类型

std::async、std::future:创建后台任务并返回值:

std::async函数模板:用来启动一个异步任务,并返回std::future对象(类模板对象)。

std::future对象中含有线程入口函数所返回的结果,能通过其成员函数get()来获取结果

// 自动创建一个“异步线程”(执行“异步任务”)并开始执行对应的线程入口函数,最终返回一个std::future对象
std::future<int> result = std::async(mythread);cout << result.get() << endl;

get()函数,一直在等待,除非拿到结果(且只能调用一次)。

std::native_handle()函数:

对操作系统的线程库进行封装,都会损失一部分功能,为了弥补c++11线程库的不足,thread类提供了native_handle()成员函数:用于获得与操作系统相关的原生线程句柄。

注:操作系统的原生线程库,就可以用原生线程句柄操作线程。

原子类型#include<atomic>

c++11提供的模板类atomic<T>,其模板参数可以是bool、int、long、long long、指针类型(不支持浮点数和自定义类型)。

原子操作介绍:

由CPU指令提供支持性能比锁和消息传递效率更高,且支持修改、读取、交换、比较并交换等操作。

成员函数:

在这里插入图片描述

/* 构造函数:*/
atomic() noexcept = default;             // 默认构造函数
constexpr atomic(T desired) noexcept;    // 转换函数
atomic(const atomic&) = delete;          // 拷贝构造函数/* 赋值函数(禁用):*/
atomic& operator=(const atomic&) = delete;         /* 常用的函数:*/
void store(T desired, std::memory order order = std::memory order seq cst) noexcept;
// desired:存储到原子变量中的值、order:强制的内存顺序/* 将desired值存入原子变量中:*/
T load(std::memory order order = std::memory order seq cst) const noexcept;
// 原子地加载并返回原子变量的当前值。按照order的值影响内存。

使用细节:

  • atomic<T>模板类的模板参数是指针,表示该指针是原子类型,但不表示它所指向的对象是原子类型。
  • atomic<T>模板类重载了整数操作的各种运算符。
  • 原子整型可用作“计数器”,布尔型可用作开关。
  • CAS指令,是实现“无锁队列”的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_185628.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter开发GridView控件详解

GridView跟ListView很类似&#xff0c;Listview主要以列表形式显示数据&#xff0c;GridView则是以网格形式显示数据&#xff0c;掌握ListView使用方法后&#xff0c;会很轻松的掌握GridView的使用方法。 在某种界面设计中&#xff0c;如果需要很多个类似的控件整齐的排列&…

这4个网站太厉害了, 每一个都能帮你打开新世界大门

又是一期满满的干货&#xff01;今天给大家推荐几个小众但超好用的网站&#xff0c;每一个都能帮你打开新世界的大门&#xff01; 一、音分轨——在线人声分离网站 在线从任何音、视频中提取人声、伴奏和各种乐器&#xff0c;针对某一视频可以单独从里面提取人声或背景音乐&am…

JAVA反射(原理+使用)

引言 反射是一种机制&#xff0c;能够使java程序在运行过程中&#xff0c;检查&#xff0c;获取类的基本信息&#xff08;包&#xff0c;属性&#xff0c;方法等&#xff09;&#xff0c;并且可以操作对象的属性和方法 反射是框架实现的基础 反射的原理 讲述反射的原理之前&a…

Java并发面试题:(七)ThreadLocal原理和内存泄漏

ThreadLocal是什么&#xff1f; ThreadLocal是线程本地存储机制&#xff0c;可以将数据缓存在线程内部。ThreadLocal存储的变量在线程内共享的&#xff0c;在线程间又是隔离的。 ThreadLocal实现原理&#xff1f; ThreadLocal的底层是ThreadLocalMap&#xff0c;每个Thread都…

【算法学习】归并算法Merge Sort总结

归并排序思路简单&#xff0c;速度仅次于快速排序&#xff0c;为稳定排序算法&#xff0c;一般用于对总体无序&#xff0c;但是各子项相对有序的数列。 1. 基本思想 归并排序使用分治思想&#xff0c;分治模式下每一层递归有三个步骤&#xff1a; 分解&#xff08;divide)&a…

drawio都能做那些事情和模板示例

drawio都能做那些事情和模板示例 你可以使用drawio&#xff0c;并使用drawio提供的扩展模板库和大量的形状库&#xff0c;针对很多不同的工业领域创建不同类型的图表。 针对如下的内容中的所有的图&#xff0c;均可以下载源文件并导入到drawio中再次编辑&#xff08;供学习者…

初学C语言,写给自己的第一个实用程序

计算器&#xff1a;第一个实用程序 在 C 语言编程的学习之路上&#xff0c;同学们在了解基本概念&#xff0c;掌握基础语法之后&#xff0c;一定跃跃欲试想要给自己开发一款有意义的实用程序。 编程实现计算器是一个不错的选择。因为它难度适中&#xff0c;需要用到的知识又恰…

1814_ChibiOS中的时间以及时间间隔处理

全部学习汇总&#xff1a; GreyZhang/g_ChibiOS: I found a new RTOS called ChibiOS and it seems interesting! (github.com) 1. 时间的相关配置&#xff0c;有tick的计数精度、时钟频率、间隔时间精度、时间类型大小等不同的配置。这些参数&#xff0c;涉及到系统的时间计数…

SystemVerilog Assertions应用指南 Chapter1.31 在属性中使用形参

可以用定义形参( formal arguments)的方式来重用一些常用的属性。属性“arb”使用了4个形参,并且根据这些形参进行检验。其中还定义了特定的时钟。SVA允许使用属性的形参来定义时钟。这样,属性可以应用在使用不同时钟的相似设计模块中。同样的,时序延迟也可以参数化,这使得属性…

Pygame中实现图片的移动

在《Pygame中将鼠标形状设置为图片2-1》和《Pygame中将鼠标形状设置为图片2-2》中提到将鼠标设置为指定图片。接下来在该文章涉及到的代码基础之上&#xff0c;实现图片的移动&#xff0c;效果如图1所示。 图1 图片移动效果 从图1中可以看出&#xff0c;导入一个大猩猩的图片&…

设计师都有些常用的组件库?

Vue通常用于构建用户界面和单页应用程序。产品开发者可以从简单的组件开始创建&#xff0c;并逐渐衍生出更复杂的前端平台。通过合理应用UI组件库&#xff0c;设计师可以快速提高整体设计效率&#xff0c;更好地优化产品从外观结构到交互体验。本节将盘点9个常用的VueUI组件库。…

ubuntu中执行一个c程序-编辑makefile文件执行make

需求&#xff1a; 编写一个由头文件greeting.h、自定义函数文件greeting.c、主函数文件myapp.c 构成的C 程序&#xff0c;并根据这三个文件的依赖关系编写Makefile 文件 greeting.h #ifndef _GREETING_H #define _GREETING_H void greeting(char *name); #endifgreeting.c …

十四、Django框架使用

目录 一、框架简介二、MVT模型简介三、Python的虚拟环境3.1 安装virtualenv 虚拟环境3.2 创建和使用虚拟环境四、Django项目的搭建4.1 安装Django包4.2 创建Django项目4.3 创建Django项目的应用4.4 使用pycharm打开Django项目4.5 注册Django项目的应用4.6 启动Django项目五、OR…

SPI学习笔记:DAC与ACD

一、SPI协议简介 SPI Serial Peripheral Interface&#xff0c;是串行外围设备接口&#xff0c;是一种高速&#xff0c;全双工&#xff0c;同步的通信总线。常规只占用四根线&#xff0c;节约了芯片管脚&#xff0c;PCB的布局省空间。现在越来越多的芯片集成了这种通信协议&a…

【Typora】解决单词爆红问题

问题&#xff1a;写笔记时&#xff0c;有许多单词爆红&#xff0c;看着十分不舒服 解决方案&#xff1a; 点击文件 --> 偏好设置 编辑器 --> 检查拼写错误&#xff0c;修改为&#xff1a;不使用拼写检查 修改好后返回界面&#xff0c;效果如下&#xff1a;

【网络】计算机网络基础概念入门

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;个人主页 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#…

页表置换算法之最佳置换算法(OPT),先进先出置换算法(FIFO),最近最久未使用置换算法(LRU),时钟置换算法(CLOCK)

请求分页存储管理与基本分页存储管理的主要区别: 在程序执行过程中&#xff0c;当所访问的信息不在内存时&#xff0c;由操作系统负责将所需信息从外存调入内存&#xff0c;然后继续执行程序。若内存空间不够&#xff0c;由操作系统负责将内存中暂时用不到的信息换出到外存。页…

2023年淘宝天猫双十一什么时候开始?天猫双十一满减活动规则和优惠力度是多少

2023年天猫淘宝双十一活动将在10月24日20时开启&#xff0c;同时包含两波正式开买时间点&#xff0c;分别为10月31日20时和11月10日20时。 一、2023天猫淘宝双十一活动时间表 第一波 (1)预售 预售预热&#xff1a;2023年10月24日14:00:00-2023年10月24日19:59:59 定金*支付…

WordPress(7)js代码增加网页底部 本站已稳定运行了X天

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、位置说明二、使用步骤1.引入库前言 提示:以下是本篇文章正文内容,下面案例可供参考 一、位置说明 比较简单的一段JS代码,直接放在要显示的位置即可, 如果你用的是lolimeow主题,直接…

【前端学习】—Promise基本知识(十六)

【前端学习】—Promise基本知识&#xff08;十六&#xff09; 一、class和function的区别 相同点&#xff1a;都可以用作构造函数不同点&#xff1a;class不可以使用call apply bind的方式来改变它的执行上下文 二、Promise 定义&#xff1a;Promise是异步编程的一种解决方案…