前言
本文介绍c++线程剩下的知识。线程安全和线程同步
一、线程安全
同一进程中的多个线程共享该进程中全部的系统资源,多个线程访问同一资源是可能会发生冲突。
这里的冲突我用demo程序演示:
#include <iostream>
#include <thread> // 线程类头文件。
using namespace std;
int aa = 0; // 定义全局变量。
// 普通函数,把全局变量aa加1000000次。
void func() {
for (int ii = 1; ii <= 1000000; ii++)
aa++;
}
int main()
{
// 用普通函数创建线程。
thread t1(func); // 创建线程t1,把全局变量aa加1000000次。
thread t2(func); // 创建线程t2,把全局变量aa加1000000次。
t1.join(); // 回收线程t1的资源。
t2.join(); // 回收线程t2的资源。
cout << "aa=" << aa << endl; // 显示全局变量aa的值。
}
这里最终的aa的结果不会是2000000,因为两个线程同时会对aa进行操作,会发生冲突。解决这个问题最常用的方法就是加锁,下面介绍。
线程安全涉及三个概念:原子性,可见性,有序性
1.原子性
原子性:所有的操作要么全部执行,要么全部不执行
2.可见性
可见性:线程操作共享资源时,通常会把该变量加载到CPU缓存,修改改变量后,CPU会立即更新缓存,但不一定会立即写入内存,如果其它线程操作内存时是访问原来那个旧数据。所以可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到
3.有序性
程序执行的顺序按照代码的先后顺序执行。
二、线程同步
线程同步是多线程编程中的一个核心概念,它涉及到在多个线程并发执行时,如何协调它们以保证它们按照一定的顺序执行并达到正确的执行结果。线程同步的目的是为了防止多个线程在访问共享资源时产生冲突,确保数据的一致性和正确性。
1.互斥锁
**C++11提供了四种互斥锁:
mutex:互斥锁。
timed_mutex:带超时机制的互斥锁。
recursive_mutex:递归互斥锁。
recursive_timed_mutex:带超时机制的递归互斥锁。
包含头文件:#include **
对上面的demo程序进行修改可以达到预期的效果
代码如下(示例):
#include <iostream>
#include <thread> // 线程类头文件。
using namespace std;
mutex mut; //创建锁对象
int aa = 0; // 定义全局变量。
// 普通函数,把全局变量aa加1000000次。
void func() {
for (int ii = 1; ii <= 1000000; ii++)
mut.lock(); //加锁
aa++;
mut.unlock() //解锁
}
int main()
{
// 用普通函数创建线程。
thread t1(func); // 创建线程t1,把全局变量aa加1000000次。
thread t2(func); // 创建线程t2,把全局变量aa加1000000次。
t1.join(); // 回收线程t1的资源。
t2.join(); // 回收线程t2的资源。
cout << "aa=" << aa << endl; // 显示全局变量aa的值。
}
这样结果就是2000000,当加锁后只有一个线程才能操作aa,其它线程都被阻塞在申请加锁中。
互斥锁还有很多种类型,如:lock_guard类,timed_mutex类,
recursive_mutex类
lock_guard是模板类,可以简化互斥锁的使用,也更安全。
lock_guard的定义如下:
template
class lock_guard
{
explicit lock_guard(Mutex& mtx);
}
lock_guard在构造函数中加锁,在析构函数中解锁。
lock_guard采用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离开作用域时自动释放)。
2.条件变量-生产消费者模型
条件变量-生产消费者模型实现细节请看我的另一篇文章
链接: 条件变量-生产消费者模型
总结
本文解释了什么是线程安全及解决线程安全的措施:线程同步。