我想编写一个程序,其中将创建随机数,我将追踪其中最大的一个。三个线程将并行运行。
我用两种方法做到了。首先,我在main()中创建一个变量,然后我通过ref传递。每个线程。最后,此变量保持生成的最大值。当变量更新时,我使用互斥锁(我真的需要吗?)。
第二种方法使用std :: atomic并产生相同的结果(据我测试它)。
这是我做的一个小例子,为了在我的项目中使用 critical ,所有线程都可以看到所有线程找到的当前最佳值。
代码:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex
#include <atomic>
#include <random>
std::default_random_engine generator((unsigned int)time(0));
int random(int n) {
std::uniform_int_distribution<int> distribution(0, n);
return distribution(generator);
}
std::mutex mtx; // mutex for critical section
std::atomic<int> at_best(0);
void update_cur_best(int& cur_best, int a, int b) {
// critical section (exclusive access to std::cout signaled by locking mtx):
if(cur_best > a && cur_best > b)
return;
if(at_best > a && at_best > b)
return;
int best;
if(a > b)
best = a;
else
best = b;
mtx.lock();
cur_best = best;
mtx.unlock();
// or
if(a > b)
at_best = a;
else
at_best = b;
}
void run(int max, int& best) {
for(int i = 0; i < 15; ++i) {
update_cur_best(best, random(max), random(max));
}
}
//g++ -std=c++0x -pthread px.cpp -o px
int main ()
{
int best = 0;
std::thread th1 (run, 100, std::ref(best));
std::thread th2 (run, 100, std::ref(best));
std::thread th3 (run, 100, std::ref(best));
th1.join();
th2.join();
th3.join();
std::cout << "best = " << best << std::endl;
std::cout << "at_best = " << at_best << std::endl;
return 0;
}
问题:
这两种方法是否相同?
来自 ref:“原子 types是封装其访问受保证的值的类型 不会导致数据争用,可用于同步内存 访问不同的线程。“
它们是否等同于 他们产生的结果和效率?
如果是,那为什么要引入原子?我应该采用什么方法 使用?速度是我感兴趣的。
有没有更快的方法来实现此功能?
请记住,对于我的实际项目,重点是best
将具有来自所有线程的当前最佳值,以便更轻松地进行比较。
答案 0 :(得分:2)
除了the data race @nwp pointed out in the comments之外,您还在generator
和distribution
上进行了数据争用:标准随机数生成器和发行版不会被多个线程同时更新。通过为每个线程创建随机数生成器/分发,可以轻松解决此问题。您还需要确保全局“当前最大值”的比较和更新是原子的,例如,使用compare_exchange_weak
(Live at Coliru):
#include <iostream> // std::cout
#include <thread> // std::thread
#include <atomic>
#include <random>
#include <array>
void update_cur_best(std::atomic<int>& best, int a, int b) {
if (a < b) {
a = b;
}
auto cur_best = best.load(std::memory_order_relaxed);
while (cur_best < a && !best.compare_exchange_weak(cur_best, a))
;
}
void run(int max, std::atomic<int>& best) {
std::mt19937 generator{std::random_device{}()};
std::uniform_int_distribution<int> distribution{0, max};
for(int i = 0; i < 15; ++i) {
update_cur_best(best, distribution(generator), distribution(generator));
}
}
//g++ -std=c++0x -pthread px.cpp -o px
int main()
{
std::atomic<int> best{0};
const int max = 100;
std::array<std::thread, 3> threads;
for (auto& t : threads) {
t = std::thread(run, max, std::ref(best));
}
for (auto& t : threads) {
t.join();
}
std::cout << "best = " << best << std::endl;
}
更新魔法在compare_exchange_weak
循环中:
auto cur_best = best.load(std::memory_order_relaxed);
while (cur_best < a && !best.compare_exchange_weak(cur_best, a))
;
当且仅当前值为best
时,它会将a
的值更新为cur_best
。否则,cur_best
会更新为当前值best
,循环会再次尝试。实际上,best
只有在其他线程在此线程尝试更新之前未将其设置为大于a
的值时才会更新。
答案 1 :(得分:0)
就使用互斥锁而言:您正在读取互斥锁之外的cur_best
并在持有互斥锁的情况下更新它 - 这不会起作用。
使用原子:
虽然读取或更新原子变量对于那些同时发生的事情是原子的,但每个单独的读取或更新都是原子的 - 原子性并不适用于不同的表达式。
因此,例如,if(at_best > a && at_best > b)
中的表达式可能会解析为true
(并且我忽略了at_best
每次都可以返回不同值的事实在线程执行时,#39;在该表达式中进行评估:
if(a > b)
at_best = a;
else
at_best = b;
原子变量at_best
可能已被另一个线程更新,而这个可能会用较小的值覆盖它。现在,另一个线程设置的较大值将永远丢失。