当不同的线程要操作同一个数据时,全部都是读的状态可以,但是当出现有写的请求时,就需要对数据进行锁定。
即一个线程使用时,另一个线程不能执行对数据的读和写
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
该代码在Qtcreate中编译运行的结果是没有问题的,但在VS2022中会报错
显示list对应的pop出错
这就是因为两个线程同时访问同一个数据,导致的数据不同步的问题,一个线程对数据进行insert操作,另一个线程执行pop操作,这样就会导致数据不一致问题
这里要用到
mutex对象 ,加锁和解锁的操作都是其内置对象
#include
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
mutex tex;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
tex.lock();
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
tex.unlock();
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
tex.lock();
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
tex.unlock();
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
经过测试证明,上面通过加锁的方式可以有效避免多线程同时修改和读取同一数据造成的数据不一致问题
std命名空间中内置了智能lock的对象,哎对象可以智能加锁和解锁,防止在加锁后忘记解锁,照成程序的停止执行
#include
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
mutex tex;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
lock_guard<mutex> gurad(tex);
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
lock_guard<mutex> gurad(tex);
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
实现原理
创建了lock_guard对象,会执行其构造函数,当函数结束后会执行其析构函数
这两个函数可以看作是mutex中的lock和unlock函数
这里的函数执行结束,就是其作用域结束,当作用域不存在时,lock_guard对象就会被释放,执行器析构函数,这样就会将锁解除
当出现两个互斥量需要加锁的时候,加锁的顺序可能会影响到程序的执行
就可能照成死锁的出现
#include
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
mutex tex1;
mutex tex2;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
tex1.lock();
tex2.lock();
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
tex1.unlock();
tex2.unlock();
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
tex2.lock();
tex1.lock();
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
tex1.unlock();
tex2.unlock();
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
inToList函数中是先将tex1进行加锁,后请求对tex2进行加锁
outToLIst函数中是先将tex2进行加锁,后请求对tex1进行加锁
这样在线程执行的时间不合适时,就会导致死锁问题
#include
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
mutex tex1;
mutex tex2;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
lock(tex1, tex2);
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
tex1.unlock();
tex2.unlock();
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
lock(tex1, tex2);
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
tex1.unlock();
tex2.unlock();
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
并且要执行unlock操作
- lock_guard()参数列表中可以添加adopt_lock,表明创建lock_guard对象时不需要再次执行lock操作
#include
#include
#include
#include
#include
using namespace std;
class LockAndUnlock
{
private:
mutable list<int> curList;
mutex tex1;
mutex tex2;
public:
void inToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
lock(tex1, tex2);
lock_guard<mutex> gurard(tex1, adopt_lock);
lock_guard<mutex> guard2(tex2, adopt_lock);
curList.push_front(i);
cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
}
}
void outToMyList()
{
for (int i = 0; i < 10000; i++)
{
//lock_guard gurad(tex);
lock(tex1, tex2);
lock_guard<mutex> gurard(tex1, adopt_lock);
lock_guard<mutex> guard2(tex2, adopt_lock);
if (!curList.empty()) {
int comb = curList.front();
cout << "当前队首的值为>: " << comb << endl;
curList.pop_front();
cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
}
else {
cout << this << this_thread::get_id() << ">: 队列为空" << endl;
}
}
}
};
int main()
{
LockAndUnlock lockAndUnlock;
thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
inThread.join();
outThread.join();
cout << this_thread::get_id() << ">: 这里是主线程" << endl;
return 1;
}
相较于lock_guard更加的智能,能够接受的参数更加多样化
拥有很多的成员函数,可以对手动对锁进行操作包括解锁和加锁
unique_lock.try_lock(): 尝试去获得锁,返回值为bool类型,获得返回true,否则返回false
参数二可接受的值
- defer_lock
将mutex和unique_lock进行绑定,但是不执行lock,但是在创建之前不能执行lock函数。判断unqiue_lock执行为true时,说明拿到了锁
这样做的目的是为了判断是否拿到锁来进行分支执行- try_to_lock
尝试其lock互斥量,在创建之前不能执行lock函数。当unique_lock.try_lock()返回值为true时,说明拿到锁了- adopt_lock
和lock_guard的使用方法是一致的,在床架guard对象的时候默认不执行lock函数,但在之前要执行lock函数
原子性:
- 对于不可再分的操作叫做原子操作
- 例如数据的赋值,递增,递减等等
这些操作在多线程中如果是共享数据,那么就要进行加锁和解锁的操作用于保证数据的安全性
但是,频繁加锁解锁会造成很大的时间消耗
这就用到了atomic类对象来定义数据- 作用就是可以不用频繁加锁和解锁,它默认会保证数据的执行封闭
先看看使用原子操作得到的数据耗时
#include
#include
#include
using namespace std;
mutex tex;
void myThread(atomic<int>& count)
{
for (int i = 0; i < 100000000; i++)
{
count += 1;
}
}
void myThread2(int& num)
{
for (int i = 0; i < 100000000; i++)
{
tex.lock();
num++;
tex.unlock();
}
}
int useOfShared_future(shared_future<int> fu)
{
cout << this_thread::get_id() << ":>正在执行" << fu.get() << endl;
return fu.get();
}
int main()
{
std::atomic<int> count(0);
clock_t startTime = clock();
thread t1(myThread,ref(count));
thread t2(myThread,ref(count));
t1.join();
t2.join();
clock_t endTime = clock();
cout << "程序耗时:> " << double(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;;
cout << "num = " << count << endl;
cout << "endl" << endl;
}

再看看使用加锁解锁的数据耗时
#include
#include
#include
using namespace std;
mutex tex;
void myThread(atomic<int>& count)
{
for (int i = 0; i < 100000000; i++)
{
count += 1;
}
}
void myThread2(int& num)
{
for (int i = 0; i < 100000000; i++)
{
tex.lock();
num++;
tex.unlock();
}
}
int useOfShared_future(shared_future<int> fu)
{
cout << this_thread::get_id() << ":>正在执行" << fu.get() << endl;
return fu.get();
}
int main()
{
std::atomic<int> count(0);
clock_t startTime = clock();
int num = 0;
//thread t1(myThread,ref(count));
//thread t2(myThread,ref(count));
thread t1(myThread2, ref(num));
thread t2(myThread2, ref(num));
t1.join();
t2.join();
clock_t endTime = clock();
cout << "程序耗时:> " << double(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;;
cout << "num = " << num << endl;
cout << "endl" << endl;
}

这样就看到了两者之间的差距了。