c++ 多线程公共变量读写竞争 thread 加锁 mutex volatile
c++ 多线程
多线程共用的公共变量往往需要为了避免读写竞争而加锁,一切实现避免读写竞争的基础是基于cpu的原子操作。所以还有使用原子变量这种方法。
下面给出常见的几种避免读写竞争的写法
一般是用mutex,下面给出三种mutex的写法:
第一种 try_lock()
自己手动找位置加锁解锁。并且使用非阻塞的方式获取锁,(尝试在不阻塞的情况下获得互斥锁的所有权)。就是代码会立即返回true或false。
#include <iostream>
#include <stdio.h>
#include <thread>
#include <mutex>
using namespace std;
typedef long long LL;
int cnt = 0;
int add_times = 20000;
mutex my_mutex;
void thread1()
{
for (LL i = 0; i < add_times; i++)
{
if(my_mutex.try_lock())
{
cnt++;
my_mutex.unlock();
}
else i--;
}
}
void thread2()
{
for (LL i = 0; i < add_times; i++)
{
if(my_mutex.try_lock())
{
cnt++;
my_mutex.unlock();
}
else i--;
}
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
cout << cnt << endl;
cout << add_times * 2 << endl;
return 0;
}
第二种 lock()
自己手动找位置加锁解锁。使用阻塞的方式获取锁,(阻塞调用线程,直到线程获得互斥锁的所有权。它的返回值被忽略)。代码的返回值是void,这个函数会一直卡住,直到拿到锁才继续运行。
#include <iostream>
#include <stdio.h>
#include <thread>
#include <mutex>
using namespace std;
typedef long long LL;
int cnt = 0;
int add_times = 20000;
mutex my_mutex;
void thread1()
{
for (LL i = 0; i < add_times; i++)
{
my_mutex.lock();
cnt++;
my_mutex.unlock();
}
}
void thread2()
{
for (LL i = 0; i < add_times; i++)
{
my_mutex.lock();
cnt++;
my_mutex.unlock();
}
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
cout << cnt << endl;
cout << add_times * 2 << endl;
return 0;
}
第三种 std::lock_guard
使用一个std::lock_guard<std::mutex> guard(my_mutex);
的临时变量。
相比于mutex功能,lock_guard具有创建时加锁,析构时解锁的功能,类似于智能指针,为了防止在线程使用mutex加锁后异常退出导致死锁的问题,建议使用lock_guard代替mutex。
这种也是阻塞式的锁,测试了一下,也容易导致一些线程长期霸占着锁。
个人感觉考虑效率的话,第一种非阻塞的方式应该更快。
鲁棒性的话,第三种锁可以自动析构解锁,会比第二种好一点。
#include <iostream>
#include <stdio.h>
#include <thread>
#include <mutex>
using namespace std;
typedef long long LL;
int cnt = 0;
int add_times = 20000;
mutex my_mutex;
void thread1()
{
for (LL i = 0; i < add_times; i++)
{
std::lock_guard<std::mutex> guard(my_mutex);
cnt++;
}
}
void thread2()
{
for (LL i = 0; i < add_times; i++)
{
std::lock_guard<std::mutex> guard(my_mutex);
cnt++;
}
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
cout << cnt << endl;
cout << add_times * 2 << endl;
return 0;
}
详细测试部分
vs的编译器对于thread的实现感觉有问题。
对于一个不加锁的多线程自增变量的代码,vs在自增量较小的时候完全不出错,但当数值大于某个值的时候就会出奇怪的错。
按理来说不加锁的话最后累加的结果应该每次都是不确定的。
测试代码如下:
#include <iostream>
#include <stdio.h>
#include <thread>
using namespace std;
typedef long long LL;
int cnt = 0;
int add_times = 200000;
void thread1()
{
for (LL i = 0; i < add_times; i++)
cnt++;
}
void thread2()
{
for (LL i = 0; i < add_times; i++)
cnt++;
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
cout << cnt << endl;
cout << add_times * 2 << endl;
return 0;
}
在vs中测试为:
200000
400000
但当累加次数小一点的时候就又会完全不出错。
40000
40000
用mingw测试结果为:
212187
400000
30824
40000
我认为mingw这种表现才算正常嘛。
一般来说想避免读写冲突,导致使用脏数据,我们需要对临界区(就是那部分需要访问公共变量的代码)进行加锁。
原子变量 解决读写竞争
除了使用锁,也可以使用原子变量做信号量,来解决读写竞争
#include <iostream>
#include <stdio.h>
#include <thread>
#include <atomic>
using namespace std;
typedef long long LL;
atomic_int cnt = 0;
int add_times = 200000;
void thread1()
{
for (LL i = 0; i < add_times; i++)
cnt++;
}
void thread2()
{
for (LL i = 0; i < add_times; i++)
cnt++;
}
int main()
{
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
cout << cnt << endl;
cout << add_times * 2 << endl;
return 0;
}