公平なmutex
最後にmutexのバリエーションをもうひとつ。
int valueを1つ用意し、複数のスレッドがひっきりなしに++valueします。もちろんデータレースを起こさないようmutexでガードをかけて。この処理を1秒間繰り返し、各スレッドが何回++valueできたかを勘定します。各スレッドの++valueした回数を、平均値を100として正規化しました。
#include <tbb/tbb.h>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <numeric>
#include <array>
using namespace std;
void run() {
const int N = 10;
tbb::spin_mutex mtx;
int value; // コレに対しN個のスレッドがアクセスする
array<int,N> count;
fill(count.begin(), count.end(), 0);
tbb::spin_rw_mutex start;
start.lock();
array<tbb::tbb_thread,N> threads;
tbb::tick_count cnt = tbb::tick_count::now();
for ( int i = 0; i < N; ++i ) {
threads[i] = tbb::tbb_thread([&](int n) {
tbb::spin_rw_mutex::scoped_lock lock(start,false); // スタートを待つ
int c = 0;
// 1秒間、共有変数valueにアクセスし
while ( (tbb::tick_count::now() - cnt).seconds() < 1.0 ) {
tbb::spin_mutex::scoped_lock lock(mtx);
++value;
++c;
}
count[n] = c; // その回数を記録する
},i);
}
start.unlock(); // 全員同時にスタート
// 全員の終了を待つ
for_each(threads.begin(), threads.end(),
[](tbb::tbb_thread& thr) { thr.join();});
// アクセス回数を、その平均を100に正規化して出力
double mean = accumulate(count.begin(), count.end(), 0.0) / count.size();
for_each(count.begin(), count.end(), [&](int c) { cout << setw(4) <<
static_cast<int>(c/mean*100.0);});
cout << " mean= " << mean << endl;
}
int main() {
for ( int i = 0; i < 10; ++i ) {
run();
}
}

スレッドの仕事量にバラツキがあります。複数のスレッドが実行権をaquireするとき、どのスレッドが獲得するかは、いわば「運次第」です。イス取りゲームやってるようなもので、運が悪いスレッドはちっとも実行権がもらえずに長い間待たされることもあります。フェア(公平)じゃないんですね。
この不公平を解消するのがqueuing_mutexです。実行権を手に入れるためacquireを開始した順に行儀よく列を作って待ちます。全体のパフォーマンスは落ちますが、実行権はそれぞれのスレッドに「ほぼ」公平に分配されます。
上記コードのtbb::spin_mutexをtbb::queuing_mutexに差し替えると、

平均アクセス数が減ってはいますが、バラツキがかなり軽減されているのが見て取れます。
並列プログラミングは正直面倒です。思いもしない現象でプログラマを泣かせてくれます。しかしながら消費電力(つまり発熱)の問題からCPUのクロック数の上昇は頭打ちであり、単一スレッドでの速度向上は見込めないのが現状で、CPUのコアを増やし並列プログラミングでパフォーマンスを稼ごうとしています。つまりはプログラマの腕次第ということです。プログラマには悩ましくも胸躍る時代になりそうです。
