我是 C++ 中线程使用的初学者。我已经阅读了有关 std::thread 和互斥锁的基础知识,似乎我了解使用互斥锁的目的。
我决定检查没有互斥锁的线程是否真的如此危险(好吧,我相信书,但更喜欢亲眼看到它)。作为“我将来不应该做的事情”的测试用例,我创建了相同概念的 2 个版本:有 2 个线程,其中一个将一个数字递增数次(NUMBER_OF_ITERATIONS),另一个将相同的数字递减相同的数量次,因此我们希望在代码执行后看到与之前相同的数字。附上代码。
起初,我运行 2 个线程,它们以不安全的方式执行 - 没有任何互斥锁,只是为了看看会发生什么。在这部分完成后,我运行 2 个线程,它们做同样的事情,但以安全的方式(使用互斥锁)。
预期结果:如果没有互斥锁,结果可能与初始值不同,因为如果两个线程同时使用数据可能会损坏数据。尤其是对于巨大的 NUMBER_OF_ITERATIONS 来说这是很常见的——因为损坏数据的可能性更高。所以这个结果我可以理解。
我还测量了“安全”和“不安全”部分花费的时间。正如我所料,对于大量迭代,安全部分花费的时间比不安全部分要多得多:互斥检查花费了一些时间。但是对于少量迭代(400、4000),安全部分执行时间小于不安全时间。为什么会这样?它是操作系统所做的事情吗?或者编译器是否有一些我不知道的优化?我花了一些时间思考它,并决定在这里问。
我使用 windows 和 MSVS12 编译器。
因此问题是:为什么安全部分的执行可能比不安全部分的第一部分更快(对于小的 NUMBER_OF_ITERATIONS < 1000*n)? 另一个:为什么它与NUMBER_OF_ITERATIONS有关:对于较小的(4000)带有互斥锁的“安全”部分更快,但对于大型(400000)的“安全”部分较慢?
主文件
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <windows.h>
//
///change number of iterations for different results
const long long NUMBER_OF_ITERATIONS = 400;
//
/// time check counter
class Counter{
double PCFreq_ = 0.0;
__int64 CounterStart_ = 0;
public:
Counter(){
LARGE_INTEGER li;
if(!QueryPerformanceFrequency(&li))
std::cerr << "QueryPerformanceFrequency failed!\n";
PCFreq_ = double(li.QuadPart)/1000.0;
QueryPerformanceCounter(&li);
CounterStart_ = li.QuadPart;
}
double GetCounter(){
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return double(li.QuadPart-CounterStart_)/PCFreq_;
}
};
/// "dangerous" functions for unsafe threads: increment and decrement number
void incr(long long* j){
for (long long i = 0; i < NUMBER_OF_ITERATIONS; i++) (*j)++;
std::cout << "incr finished" << std::endl;
}
void decr(long long* j){
for (long long i = 0; i < NUMBER_OF_ITERATIONS; i++) (*j)--;
std::cout << "decr finished" << std::endl;
}
///class for safe thread operations with incrment and decrement
template<typename T>
class Safe_number {
public:
Safe_number(int i){number_ = T(i);}
Safe_number(long long i){number_ = T(i);}
bool inc(){
if(m_.try_lock()){
number_++;
m_.unlock();
return true;
}
else
return false;
}
bool dec(){
if(m_.try_lock()){
number_--;
m_.unlock();
return true;
}
else
return false;
}
T val(){return number_;}
private:
T number_;
std::mutex m_;
};
///
template<typename T>
void incr(Safe_number<T>* n){
long long i = 0;
while(i < NUMBER_OF_ITERATIONS){
if (n->inc()) i++;
}
std::cout << "incr <T> finished" << std::endl;
}
///
template<typename T>
void decr(Safe_number<T>* n){
long long i = 0;
while(i < NUMBER_OF_ITERATIONS){
if (n->dec()) i++;
}
std::cout << "decr <T> finished" << std::endl;
}
using namespace std;
// run increments and decrements of the same number
// in threads in "safe" and "unsafe" way
int main()
{
//init numbers to 0
long long number = 0;
Safe_number<long long> sNum(number);
Counter cnt;//init time counter
//
//run 2 unsafe threads for ++ and --
std::thread t1(incr, &number);
std::thread t2(decr, &number);
t1.join();
t2.join();
//check time of execution of unsafe part
double time1 = cnt.GetCounter();
cout <<"finished first thr" << endl;
//
// run 2 safe threads for ++ and --, now we expect final value 0
std::thread t3(incr<long long>, &sNum);
std::thread t4(decr<long long>, &sNum);
t3.join();
t4.join();
//check time of execution of safe part
double time2 = cnt.GetCounter() - time1;
cout << "unsafe part, number = " << number << " time1 = " << time1 << endl;
cout << "safe part, Safe number = " << sNum.val() << " time2 = " << time2 << endl << endl;
return 0;
}