C++之std::atomic解决多线程7个问题(二百四)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!

优质专栏:Audio工程师进阶系列原创干货持续更新中……

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

欢迎关注Android系统攻城狮

1.前言

本篇目的:理解C++之std::atomic原子操作解决多线程竟态问题用法。

2.std::atomic可以解决的C++多线程问题

在C++中,std::atomic提供了一种线程安全的访问原子类型的机制,可以解决以下七个多线程之间的问题:

  1. 竞态条件(Race Condition):多个线程同时访问和修改同一个变量时可能导致数据错误。
  2. 内存可见性(Memory Visibility):不同线程对共享变量的修改可能不可见,导致读取到过期的值。
  3. 乱序执行(Out-of-Order Execution):处理器可能以不同的顺序执行指令,导致结果的不确定性。
  4. 死锁(Deadlock):多个线程相互等待对方释放资源,导致程序无法继续执行。
  5. 活锁(Live Lock):多个线程相互响应对方的动作,而无法继续向前推进。
  6. 数据竞争(Data Race):多个线程同时访问和修改共享数据,没有同步机制可能导致不确定的行为。
  7. 优先级反转(Priority Inversion):高优先级任务被低优先级任务持续占用共享资源,导致高优先级任务无法及时执行。

3.应用实例

  1. 竞态条件(Race Condition):

v1.0 未使用atomic原子操作

#include 
#include 
#include 

//std::atomic counter(0); // 原子变量
int counter = 0; // 普通变量

void increment() {
    for (int i = 0; i < 1000; i++) {
        counter++; // 原子操作:递增
	printf("counter = %d\n",counter);//打印出来的数据是乱序的
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();
    return 0;
}

v2.0 使用atomic原子操作

#include 
#include 

std::atomic<int> counter(0); // 原子变量

void increment() {
    for (int i = 0; i < 1000; i++) {
        counter++; // 原子操作:递增
        printf("counter = %d\n",counter.load());//按顺序打印0 - 2000.	
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();
    return 0;
}

多个线程同时对counter进行递增操作,使用std::atomic可以避免竞态条件,保证递增操作的原子性。

  1. 内存可见性(Memory Visibility):
#include 
#include 
#include 

std::atomic<bool> flag(false); // 原子标志

void run() {
  while (!flag.load()) {
    // do something
  }


  printf("flag = %d\n",flag.load());
}

int main() {
  std::thread t(run);

  // 假设这里执行一些耗时的计算
  std::this_thread::sleep_for(std::chrono::seconds(1));

  flag.store(true); // 原子操作:标志位置为true

  t.join();

  return 0;
}

flag作为一个标志位,在一个线程中修改为true,其他线程可以通过读取flag的原子操作来感知到修改,保证内存的可见性。

  1. 乱序执行(Out-of-Order Execution):
#include 
#include 
#include 

std::atomic<int> value(0); // 原子变量

void write() {
    value.store(42, std::memory_order_relaxed); // 原子操作:无序存储
}

void read() {
    while (value.load(std::memory_order_relaxed) == 0) {
        // do something
    }
    
    std::cout << "Value: " << value.load(std::memory_order_relaxed) << std::endl;
}

int main() {
    std::thread t1(write);
    std::thread t2(read);

    t1.join();
    t2.join();

    return 0;
}

通过使用适当的内存顺序(memory_order)来进行原子操作,可以避免乱序执行带来的问题,例如使用std::memory_order_relaxed来指定无序存储。

  1. 死锁(Deadlock):
#include 
#include 
#include 

std::atomic<bool> flag1(false);
std::atomic<bool> flag2(false);

std::mutex mutex1;
std::mutex mutex2;

void process1() {
    mutex1.lock(); // 获取锁1
    
    // 假设这里执行一些操作

    flag1.store(true); // 标记为已处理

    mutex2.lock(); // 获取锁2

    // 执行需要锁2的操作

    mutex2.unlock();
    mutex1.unlock();
}

void process2() {
    mutex2.lock(); // 获取锁2

    // 假设这里执行一些操作
  
    flag2.store(true); // 标记为已处理

    mutex1.lock(); // 获取锁1

    // 执行需要锁1的操作

    mutex1.unlock();
    mutex2.unlock();
}

int main() {
    std::thread t1(process1);
    std::thread t2(process2);

    t1.join();
    t2.join();

    return 0;
}

多个线程对两个互斥量(mutex)进行获取操作,可能导致死锁,std::atomic不能直接解决死锁问题,但可以用来作为线程间的通信机制。

  1. 活锁(Live Lock):
#include 
#include 
#include 

std::atomic<bool> flag1(false);
std::atomic<bool> flag2(false);

void process1() {
    while (!flag2) {
        // 假设这里执行一些操作
        // 暂停一段时间,避免忙等待
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        flag1.store(true);
        // 假设这里还有其他判断逻辑
    }
}

void process2() {
    while (!flag1.load()) {
        // 假设这里执行一些操作
        // 暂停一段时间,避免忙等待
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        flag2.store(true);
        // 假设这里还有其他判断逻辑
    }
}

int main() {
    std::thread t1(process1);
    std::thread t2(process2);

    t1.join();
    t2.join();

    std::cout << "Live lock detected!" << std::endl;

    return 0;
}

两个线程互相等待对方设置标志位,但由于不断地执行操作和忙等待,两个线程无法继续前进,造成活锁。

  1. 数据竞争(Data Race):
#include 
#include 
#include 

std::atomic<int> counter(0); // 原子计数器

void increment() {
    for (int i = 0; i < 1000; i++) {
        int temp = counter.load(); // 读取计数器的当前值
        counter.store(temp + 1);   // 原子操作:递增
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter.load() << std::endl;

    return 0;
}

两个线程同时读取和修改计数器的值,使用std::atomic可以避免数据竞争,保证计数器的正确递增。

  1. 优先级反转(Priority Inversion):
#include 
#include 
#include 

std::atomic<int> sharedResource(0); // 共享资源

void lowPriorityThread() {
    while (true) {
        // 低优先级任务需要访问共享资源
        while (sharedResource.load() == 0) {
            // do something
        }

        std::cout << "Low priority thread accessing shared resource." << std::endl;

        // 假设这里执行一些低优先级任务的操作

        // 完成低优先级任务后释放共享资源
        sharedResource.store(0);
    }
}

void highPriorityThread() {
    // 高优先级任务需要持有共享资源
    sharedResource.store(1);

    std::cout << "High priority thread acquired shared resource." << std::endl;

    // 假设这里执行一些高优先级任务的操作

    // 完成高优先级任务后释放共享资源
    sharedResource.store(0);
}

int main() {
    std::thread t1(lowPriorityThread);
    std::thread t2(highPriorityThread);

    t1.join();
    t2.join();

    return 0;
}

你可能感兴趣的:(C++入门系列,c++,开发语言)