C++多线程,线程安全管理
多线程同时访问一个内存地址,进行读写,
可能会出现以下四种情况:(瞎猜的,不具备任何经验时的猜测)
1、软件卡死(死锁)
2、软件闪退,非法访问内存空间,代码崩溃;
3、程序正常结束,但是算出来的值有问题;
4、程序正常结束,算出来的值也OK;
测试结论:程序正常结束,但是算出来的值有问题;
1、print_thread_id函数,对同一个内存地址进行读写
2、外层开i_thread_num个线程;
#include <stdio.h>
#include <iostream>
#include <boost/thread.hpp>
// extern "C"
// void useCUDA();
// 线程执行的函数
void print_thread_id(int * var) {
boost::thread::id this_id = boost::this_thread::get_id();
for(int i = 0; i < 100000; i++)
{
var[0]++;
}
// std::cout << "Thread ID: " << this_id << ", addr=" << &var[0] << ", value=" << var[0] << std::endl;
}
int main()
{
std::cout<<"Hello C++"<<std::endl;
int a = 0;
std::cout << "addr=" << &a << std::endl;
int i_thread_num = 10;
boost::thread_group threadArray;
for(int i_thread_idx = 0; i_thread_idx < i_thread_num; i_thread_idx++)
{
threadArray.create_thread(boost::bind(&print_thread_id, &a));
}
threadArray.join_all();
std::cout << "Final value=" << a <<std::endl;
// useCUDA();
return 0;
}
实验1,print_thread_id 内层循环100000
程序的输出应该是:100000 * 10
实际结果:
addr=0000000000DEFE30
Final value=271702
结论:多线程读写,不安全
实验2:print_thread_id 内层循环10000
程序的输出应该是:10000 * 10
实际结果:
addr=00000000003EFA90
Final value=96754
结论:多线程读写,不安全
实验3:print_thread_id 内层循环1000
程序的输出应该是:1000 * 10
实际结果(第一次):
addr=000000000038FA90
Final value=9336
实际结果(第二次):
addr=0000000000CFF710
Final value=10000
结论:多线程读写,不安全
实验4:print_thread_id 内层循环100
程序的输出应该是:100 * 10
实际结果:(第一次):
addr=0000000000BCFEF0
Final value=1000
实际结果:(第二次):
addr=00000000008FFC70
Final value=1000
实际结果:(第三次):
addr=0000000000AFF7C0
Final value=1000
结论:在读写次数比较少的时候,异常结果出现的可能性也比较小。但是从设计原理上讲,不推荐多线程对同一个内存空间,进行读写。
C++ 多线程同时读写同一个内存地址时,如何保证线程安全?
在C++中,当多个线程同时读写同一个内存地址时,必须采取措施来保证线程安全,以避免数据竞争、条件竞争等问题,这些问题可能导致未定义行为、数据损坏或程序崩溃。以下是一些常用的方法来保证线程安全:
-
互斥锁(Mutex):
互斥锁是最常用的同步机制之一。它可以确保同一时刻只有一个线程可以访问被保护的代码段(临界区)。当线程进入临界区前,必须先获取互斥锁;离开临界区时,释放互斥锁。#include <mutex> std::mutex mtx; void safe_function() { std::lock_guard<std::mutex> lock(mtx); // 临界区:访问共享资源 }
std::lock_guard
是一个RAII(Resource Acquisition Is Initialization)风格的锁管理器,它在构造时自动加锁,在析构时自动解锁,非常方便。 -
读写锁(Reader-Writer Locks):
如果共享数据被读取的频率远高于写入的频率,使用读写锁可以提高性能。读写锁允许多个线程同时读取数据,但写入时必须独占访问。#include <shared_mutex> std::shared_mutex rw_mtx; void read_function() { std::shared_lock<std::shared_mutex> lock(rw_mtx); // 读取共享资源 } void write_function() { std::unique_lock<std::shared_mutex> lock(rw_mtx); // 写入共享资源 }
-
原子操作(Atomic Operations):
对于简单的数据类型(如整数、指针等),可以使用C++11引入的<atomic>
库中的原子类型来保证操作的原子性。原子操作不可分割,执行过程中不会被线程调度机制中断。#include <atomic> std::atomic<int> count = 0; void increment() { ++count; // 原子操作 }
-
条件变量(Condition Variables):
虽然条件变量本身不直接用于保护共享数据,但它通常与互斥锁一起使用,用于实现线程间的同步,特别是在等待某个条件成立时。#include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // 执行后续操作 } void set_ready() { std::lock_guard<std::mutex> lock(mtx); ready = true; cv.notify_one(); // 或 notify_all() }
-
无锁编程(Lock-free Programming):
无锁编程是一种高级技术,通过原子操作和特定的算法设计来避免使用锁,从而提高性能。但是,它实现起来较为复杂,且容易出错,需要深入理解并发编程的原理。
每种方法都有其适用场景,通常需要根据具体的应用场景和性能要求来选择最合适的方法。在实际开发中,应尽量避免在多个线程间共享数据,或尽量将共享数据的访问限制在最小范围内,以降低并发编程的复杂性和出错率。
https://blog.youkuaiyun.com/XiaoH0_0/article/details/97411942
https://blog.youkuaiyun.com/XiaoH0_0/article/details/97411942