多线程基础知识(四):自解锁+原子操作
前言
之前的文章我们已经介绍了锁的使用与消耗
但是在日常使用中,我们可能会遇到比较复杂的业务
很多经验告诉我们会忘记释放锁,这样就会导致程序阻塞
一、自解锁使用
- 自解锁:在离开当前作用域就会自己unlock
void workFun(int index)
{
for (int n = 0; n < 20000000; ++n)
{
lock_guard<mutex> lg(m);//自解锁:在离开当前作用域就会自己unlock
sum++;
}
//cout << index << "Hello,other thread." << n << endl;
}
二、自解锁消耗测试
1、Debug86 -> 每个线程2000万次计算
2、Release86 -> 每个线程2000万次计算
3、消耗测试结论
自解锁的消耗要比lock和unlock方式大一些
三、lock_guard原理剖析
1、lock_guard源码
// CLASS TEMPLATE lock_guard
template <class _Mutex>
class lock_guard { // class with destructor that unlocks a mutex
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { // construct but don't lock
}
~lock_guard() noexcept {
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
2、原理分析
在构造的时候调用了
_MyMutex.lock();
在析构的时候调用了_MyMutex.unlock();
也就是说离开作用域后,lock_guard自动析构了
这样就实现了自解锁
相比lock和unlock增加的消耗主要因为lock_guard<mutex> lg(m);
多了创建对象
这样虽然消耗大,但是相对于lock和unlock更安全
四、原子操作
1、原子操作概念
- 原子:不可分割的操作
- 锁主要用来锁定一些比较复杂的操作
- 如果是之前这种多线程只是计算sum这种比较基础的数据,使用锁的话消耗就比较大
2、使用原子操作优化
- 源码定义:
using atomic_int = atomic<int>;
#include<atomic>
mutex m;
const int tCount = 4;
atomic_int sum = 0;
void workFun(int index)
{
for (int n = 0; n < 20000000; ++n)
{
sum++;
}
//cout << index << "Hello,other thread." << n << endl;
}
五、原子操作测试
1、Debug86 -> 2000万
2、Release86 -> 2000万
3、结论分析
- 使用原子操作的计算结果是8000万,是正确的
- 对比Debug和Release,我们发现原子操作的消耗从2958ms降低到了385ms
六、原子操作的反汇编
- 对比以下的测试结果,我们可以发现
- Debug模式下,原子操作的sum++反汇编使用了原子模板的操作
- Release模式下,原子操作的sum++反汇编增加了lock inc的操作
1、Debug86 -> int sum
{
sum++;
00E76883 mov eax,dword ptr [sum (0E842E8h)]
00E76888 add eax,1
00E7688B mov dword ptr [sum (0E842E8h)],eax
}
2、Debug86 -> atomic_int sum
{
{
sum++;
002C43E3 push 0
002C43E5 mov ecx,offset sum (02D42E8h)
002C43EA call std::_Atomic_integral<int,4>::operator++ (02C16B3h)
}
3、Release86 -> atomic_int sum
00BF1130 mov eax,1312D00h
{
sum++;
00BF1135 lock inc dword ptr [sum (0BF5408h)]
for (int n = 0; n < 20000000; ++n)
00BF113C sub eax,1
00BF113F jne workFun+5h (0BF1135h)
}
七、线程安全与线程不安全
针对
cout << index << "Hello,other thread." << n << endl;
这种连续操作的,没有原子操作的概念,只有线程安全与线程不安全的概念每个线程中尽量避免尽量减少:共享数据
基础数据类型作为共享数据的时候,使用原子操作
八、完整源码
1、main.cpp
#include<iostream>
#include<thread>
#include<mutex>
#include<atomic>
#include"CELLTimestamp.hpp"
using namespace std;
mutex m;
const int tCount = 4;
atomic_int sum = 0;
void workFun(int index)
{
for (int n = 0; n < 20000000; ++n)
{
sum++;
}
//cout << index << "Hello,other thread." << n << endl;
}
int main()
{
thread t[tCount];
for (int n = 0; n < tCount; ++n)
{
t[n] = thread(workFun, n);
}
CELLTimestamp tTime;
for (int n = 0; n < tCount; ++n)
{
t[n].join();
}
cout << tTime.getElapsedTimeInMilliSec() << ",sum=" << sum << endl;
sum = 0;
tTime.update();
for (int n = 0; n < 80000000; ++n)
{
sum++;
}
cout << tTime.getElapsedTimeInMilliSec() << ",sum=" << sum << endl;
cout << "Hello,main thread." << endl;
return 0;
}
2、CELLTimestamp.hpp
#ifndef _CELLTimestamp_hpp_
#define _CELLTimestamp_hpp_
//#include <windows.h>
#include<chrono>
using namespace std::chrono;
class CELLTimestamp
{
public:
CELLTimestamp()
{
//QueryPerformanceFrequency(&_frequency);
//QueryPerformanceCounter(&_startCount);
update();
}
~CELLTimestamp()
{}
void update()
{
//QueryPerformanceCounter(&_startCount);
_begin = high_resolution_clock::now();
}
/**
* 获取当前秒
*/
double getElapsedSecond()
{
return getElapsedTimeInMicroSec() * 0.000001;
}
/**
* 获取毫秒
*/
double getElapsedTimeInMilliSec()
{
return this->getElapsedTimeInMicroSec() * 0.001;
}
/**
* 获取微妙
*/
long long getElapsedTimeInMicroSec()
{
/*
LARGE_INTEGER endCount;
QueryPerformanceCounter(&endCount);
double startTimeInMicroSec = _startCount.QuadPart * (1000000.0 / _frequency.QuadPart);
double endTimeInMicroSec = endCount.QuadPart * (1000000.0 / _frequency.QuadPart);
return endTimeInMicroSec - startTimeInMicroSec;
*/
return duration_cast<microseconds>(high_resolution_clock::now() - _begin).count();
}
protected:
//LARGE_INTEGER _frequency;
//LARGE_INTEGER _startCount;
time_point<high_resolution_clock> _begin;
};
#endif // !_CELLTimestamp_hpp_