アトミック #2
プロシージャコールがループに変わってしまう末尾最適化を"たいしたことない"と言い切ってしまう強さには敬服します。
えー、違う違う。末尾呼び出し最適化は関数呼び出しをループじゃなくて、ジャンプに変換する。末尾呼び出しする関数が自身じゃなくても最適化できることに注意。で、それこそ人間でも機械的にできるんだし、本来 call のところが、ローカル変数いじって jmp するだけなんだからたいしたことないでしょ。
それって結局
The_Value++ ;
っていうコードは
という風にコンパイルされて、コンパイル後のコードそれぞれの行間でディスパッチ(割り込み)が起きうるよ、という話を日本語に翻訳しただけの話だよね。んで、これが本質である以上、いわゆるマシン語の知識は必須だよね、プロなら。
ちーがーうー。そんな適当な説明していると「ロックの取得は高コストだから」とかいって、新人がひどいコード書いちゃいますよ?例えばこんなの(コードは C++)。
void atomic_inc(int &n) { __asm__("incl %0" :"=m"(n)); }
x86_64 で gcc -O2 でコンパイルするとこうなる。
incl (%rdi) retq
でも、このコードは全然だめ。「命令が複数あるからアトミックじゃない」わけじゃなくて、一命令でもアトミックじゃないわけですよ。いや、そんなことはわかっているとは思うんだけど。重要なのは機械語レベルでどういう実装になっているかじゃなくて、もう少し一般的なモデルに関する知識だったり、具体的なノウハウなんじゃないかなぁ。まじめに議論しようとすると、それこそ TAS、CAS、LL/SC だの言い出さないといけない。
でだ。世の中のプログラマがみんな C でマルチスレッドプログラム組んでいるわけじゃない訳ですよ。Java ではマルチスレッドも普通に行うけど、そこで必要なのは
で、それはまぁ、機械語レベルの知識があった方が理解は速いとは思うけど、必須かと言われれば、それはどうかなぁ。高級言語は高級言語で排他制御機構を持っていたりするわけで、それはその言語が規定している機構そのものを理解しないといけない。Erlang なんか変数を共有しないから排他制御なんてしないし。
機械語レベルの知識が必要な現場があるのもわかるし、知識があると自己解決できる場面があるのもわかる。じゃあ、トラブルがあったときに全部自己解決しないといけないかといえばそうでもなくて*1、それはそういうレイヤを得意としている人に丸投げする、という選択肢もあり得る。例えば、LL でプログラムを書いていて特定のアークテクチャで問題がよくわからない挙動を示したら、普通にバグレポートを投げりゃいいじゃん。と、まあそういうスタンスな訳ですよ。
ついでに、上のコードに問題があることを示すためのテストコードでも。
erb で書いているので適当に zsh で
% erb t.erb > t.c % gcc -O2 t.c -lpthread % repeat 10 ./a.out
とでも。ちなみに Athlon 64 X2 4400+ だとこうなった。
8250157 8835858 7972059 9800634 7854913 8665203 9177237 9385040 9473736 8337679
シングルコアのシングルCPUなら問題は起こらない。
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #define NUM_THREADS 100 static void *run(void*); static volatile int counter; static pthread_mutex_t mutex; static pthread_cond_t cond; int main() { int i; pthread_t threads[NUM_THREADS]; if (pthread_mutex_init(&mutex, NULL) != 0) { perror("perror_mutex_create"); exit(errno); } if (pthread_cond_init(&cond, NULL)) { perror("pthread_cond_init"); exit(errno); } for (i = 0; i < NUM_THREADS; i++) { if (pthread_create(&threads[i], NULL, &run, NULL) != 0) { perror("pthread_create"); exit(errno); } } sleep(1); counter = 0; pthread_mutex_lock(&mutex); pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); for (i = 0; i < NUM_THREADS; i++) { if (pthread_join(threads[i], NULL) != 0) { perror("pthread_join"); } } pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); printf("%d\n", counter); return 0; } static void *run(void *param __attribute__((__unused__))) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); <% 100000.times do %> __asm__("incl %0" : "=m"(counter)); <% end %> return NULL; }
*1:さすがにスレッド間の排他・同期制御ぐらいは理解してもらわないとあれだけど