Предположим, у нас есть следующий код, который подсчитывает, сколько раз что-то происходит:
int i=0;
void f() {
// do stuff . . .
if(something_happens) ++i;
}
int main() {
std::vector<std::thread> threads;
for(int j = 0; j< std::thread::hardware_concurrency(); ++j) {
threads.push_back(std::thread(f));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread_join));
std::cout << "i = " << i << '\n';
}
В его нынешнем виде на гонке есть четкое условие гонки. Используя C ++ 11, что является (1) самым простым методом устранения этого состояния гонки и (2) самым быстрым способом? Предпочтительно без использования мьютексов. Благодарю.
Обновление: используя комментарий, чтобы использовать атомикс, я получил рабочую программу, которая компилируется под компилятором Intel, версия 13:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <algorithm>
std::atomic<unsigned long long> i = 0;
void f(int j) {
if(j%2==0) {
++i;
}
}
int main() {
std::cout << "Atomic i = " << i << "\n";
int numThreads = 8; //std::thread::hardware_concurrency() not yet implemented by Intel
std::vector<std::thread> threads;
for(int k=0; k< numThreads; ++k) {
threads.push_back(std::thread(f, k));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
std::cout << "Atomic i = " << i << "\n";
}
Вы можете посмотреть в атомные типы. Вы можете получить к ним доступ без необходимости блокировки / мьютекса.
Мы решили аналогичную проблему, объявив массив [nThreads], затем мы дали каждому потоку идентификатор в диапазоне от 0-n, после чего поток может безопасно писать в своей позиции в массиве. Затем вы можете просто суммировать массив, чтобы получить общую сумму. Это, однако, полезно только до тех пор, пока вам не нужно суммировать массив до того, как все потоки будут мертвы.
Чтобы быть еще более эффективным, у нас был локальный счетчик в каждом потоке, который мы затем перед тем, как поток умер, добавляли в массив.
пример (псевдокод 🙂
counter[nThreads];
thread(int id)
{
// Do stuff
if(something happened)
counter[id]++;
}
или же
counter[nThreads];
thread(int id)
{
int localcounter = 0;
//Do stuff
if(something happened)
localcounter++;
//Thread is about to die
counter[id] = localcounter;
}
Вы можете использовать InterlockedIncrement функция.
Многие функции для преобразования переменных атомарными способами задокументированы в MSDN под флагом Функции синхронизации — они могут быть полезны для вас.
это не просто состояние гонки, вы не можете иметь никакого представления о фактическом i
значение вообще между потоками, если ваш компилятор решит так.
очевидно atomic
это хороший способ. Мьютекс — это также хороший способ, когда у вас нет столкновений, они так же быстры, как атомные. Они становятся медленнее, только когда им действительно нужно заставить ядро возиться с sleeping
а также ready
очереди потоков. Что может получить такси, если сигнал ожидания не использует condition variable
в этом случае вам, возможно, придется подождать тик ядра графика для вашего ready
темы быть running
, который может быть очень длинным (30 мс).
Атомика даст вам оптимальность, и даже может быть легче поддерживать, чем condition variable
с благодаря тому, что не нужно заботиться о spurious
события и notify_one
против notify_all
и т.п.
Если вы проверите базовые классы shared_ptr STL, созданные в C ++ 11, они содержат base_count
или (base_shared_count или что-то еще), который работает именно так, как вам нужно.
Вы также можете проверить новую реализацию boost :: shared_count, если хотите.