在 C++ 中,可以使用以下几种方式来确保线程安全:
- 使用互斥量(mutex)来对共享资源进行保护。互斥量可以用来防止多个线程同时访问共享资源,从而避免数据竞争的问题。
- 使用读写锁(reader-writer lock)来对共享资源进行保护。读写锁允许多个读线程同时访问共享资源,但是写线程必须独占资源。这样可以在保证线程安全的同时,也尽可能地提高系统的并发性。
- 使用原子操作来对共享资源进行保护。在 C++ 中,可以使用 std::atomic 类型来定义原子变量,并使用原子操作来对共享资源进行操作。这样可以确保在多线程环境中,原子变量的操作是安全的。
- 使用条件变量(condition variable)来协调线程间的协作。条件变量可以用来在线程之间传递信号,从而控制线程的执行流程。
- 使用线程本地存储(thread-local storage)来保存线程的私有数据。线程本地存储可以用来给每个线程分配独立的存储空间,从而避免数据冲突的问题。
1.使用互斥量(mutex)来保护共享资源:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex g_mutex; // 全局互斥量
int g_counter = 0; // 共享资源
void incrementCounter()
{
std::lock_guard<std::mutex> lock(g_mutex); // 上锁
++g_counter;
}
int main()
{
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "g_counter = " << g_counter << std::endl;
return 0;
}
- 在这个例子中,我们定义了一个全局互斥量 g_mutex 和一个共享资源 g_counter。然后在 incrementCounter 函数中,我们使用 std::lock_guard 对 g_mutex 进行加锁。这样可以保证在同一时刻,只有一个线程可以访问 g_counter。
- 在主函数中,我们创建了两个线程 t1 和 t2,并让它们都执行 incrementCounter 函数。由于 g_mutex 加锁的作用,所以只有一个线程能够修改 g_counter 的值,因此最终的结果就是 g_counter = 2。
2. 使用读写锁(reader-writer lock)来保护共享资源:
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex g_mutex; // 全局读写锁
int g_counter = 0; // 共享资源
void incrementCounter()
{
std::unique_lock<std::shared_mutex> lock(g_mutex); // 上写锁
++g_counter;
}
void readCounter()
{
std::shared_lock<std::shared_mutex> lock(g_mutex); // 上读锁
std::cout << "g_counter = " << g_counter << std::endl;
}
int main()
{
std::thread t1(incrementCounter);
std::thread t2(readCounter);
t1.join();
t2.join();
return 0;
}
- 在这个例子中,我们定义了一个全局读写锁 g_mutex 和一个共享资源 g_counter。然后在 incrementCounter 函数中,我们使用 std::unique_lock 对 g_mutex 进行加写锁。这样可以保证在同一时刻,只有一个线程可以修改 g_counter 的值。
- 在 readCounter 函数中,我们使用 std::shared_lock 对 g_mutex 进行加读锁。这样可以保证在同一时刻,可以有多个线程同时读取 g_counter 的值,但是写线程必须等待所有的读线程结束后才能执行。
3.使用原子操作来保护共享资源:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> g_counter{0}; // 原子变量
void incrementCounter()
{
++g_counter;
}
int main()
{
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "g_counter = " << g_counter.load() << std::endl;
return 0;
}
- 在上面的例子中,我们定义了一个共享资源 g_counter,并使用 std::atomic 类型声明。然后在 incrementCounter 函数中,我们使用了 ++g_counter 进行原子操作,以保证在多线程环境下访问 g_counter 是安全的。
- 在主函数中,我们创建了两个线程 t1 和 t2,分别执行 incrementCounter 函数。由于 g_counter 是一个原子类型,所以在多线程环境下对其进行操作是安全的。最后,我们在主线程中输出了 g_counter 的值,显示了线程安全的结果。
- 请注意,在使用原子操作时,需要根据具体情况选择合适的原子类型。例如,如果需要操作整型,可以使用 std::atomic 类型;如果需要操作布尔型,可以使用 std::atomic 类型。
4.使用条件变量(condition variable)来协调线程间的协作:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex g_mutex;
std::condition_variable g_cv;
bool g_flag = false;
void thread1()
{
std::unique_lock<std::mutex> lock(g_mutex);
g_flag = true;
g_cv.notify_one(); // 通知线程 2
}
void thread2()
{
std::unique_lock<std::mutex> lock(g_mutex);
while (!g_flag)
{
g_cv.wait(lock); // 等待通知
}
std::cout << "thread 2 finished" << std::endl;
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
return 0;
}
- 在这个例子中,我们定义了一个互斥量 g_mutex 和一个条件变量 g_cv。我们还定义了一个全局变量 g_flag,用于标记某个条件是否满足。
- 在线程 1 中,我们将 g_flag 设为 true,并使用 g_cv.notify_one() 函数通知线程 2。
- 在线程 2 中,我们使用 while (!g_flag) 循环检测 g_flag 的值。如果 g_flag 为 false,则使用 g_cv.wait(lock) 函数等待通知,否则执行后续的操作。
- 当线程 1 通知线程 2 时,线程 2 将被唤醒,并继续往下执行。最终,线程 2 会输出 “thread 2 finished”。
- 通过这个例子,我们可以看到,使用条件变量可以在线程间协调协作,使得线程可以根据某些条件的改变而被唤醒或等待。
5.使用线程本地存储(thread-local storage)来保存线程的私有数据:
#include <iostream>
#include <thread>
thread_local int g_counter = 0; // 线程本地变量
void incrementCounter()
{
++g_counter;
std::cout << std::this_thread::get_id() << ": " << g_counter << std::endl;
}
int main()
{
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
return 0;
}
- 在这个例子中,我们使用了 thread_local 关键字声明了线程本地变量 g_counter。在主函数中,我们创建了两个线程 t1 和 t2,分别执行 incrementCounter 函数。
- 在 incrementCounter 函数中,我们使用了 ++g_counter 对 g_counter 进行了修改。由于 g_counter 是线程本地变量,所以每个线程都有自己的 g_counter,互不干扰。
- 最后,我们使用了 std::cout << std::this_thread::get_id() << ": " << g_counter << std::endl; 输出了每个线程的线程 ID 和 g_counter 的值。
- 编译并运行这个程序,可以看到每个线程的 g_counter 都是线程本地的,互不干扰。这就是使用线程本地存储来保存线程的私有数据的例子。
文章来源地址https://www.toymoban.com/news/detail-415978.html
文章来源:https://www.toymoban.com/news/detail-415978.html
到了这里,关于C++中确保线程安全的几种方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!