これは昔ながらのトピックですが、ほとんどの議論はうまくいきません。
議論の中核の大部分は、共有変数へのアクセスを同期するためのロックを設計する方法です。これは実際にはカートを馬の前に置いています:
- 交通灯を設計するのではなく、高架を設計する必要があります。
実際、マルチスレッドプログラミングは共有変数にアクセスできないようにする必要があります。マルチスレッドで共有変数に本当にアクセスしたい場合、唯一の効果的な解決策はタイミングを厳密に制御することです。さて、先着順が唯一の方法です。そのようなロックの設計に関しては、問題を防ぐためだけに、それは完全に怠惰です。
100年以上前には、同じ電話回線で異なる音声チャネルを送信することが可能でした。これは、厳密なタイムスロットの割り当てと多重化メカニズムの恩恵を受けました。その後、時代が進むにつれて事態は悪化しました。これは完全に別の原因によるものです。タイムスロット多重化の1つのタイプは、タイムスロット統計多重化によって引き起こされます。最新のオペレーティングシステムと最新のパケットスイッチングネットワークは、この多重化方式の忠実な実践者です。
統計的再利用は効率的な方法ではないと思います。それは、さまざまなシナリオに直面して採用しなければならない解決策かもしれません。私の意見では、高効率についてのみ議論するのであれば、厳密なタイムスロット多重化に勝るものはありません。
例を挙げましょう。4つのスレッドが共有変数にアクセスします。
まず、アクセス順序を厳密に割り当てて、少し厳密な計画を見てください。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem1;
sem_t sem2;
sem_t sem3;
sem_t sem4;
unsigned long cnt = 0;
#define TARGET 0xffffff
void do_work()
{
int i;
for(i = 0; i < TARGET; i++) {
cnt ++;
}
}
void worker_thread1(void)
{
sem_wait(&sem1);
do_work();
sem_post(&sem2);
}
void worker_thread2(void)
{
sem_wait(&sem2);
do_work();
sem_post(&sem3);
}
void worker_thread3(void)
{
sem_wait(&sem3);
do_work();
sem_post(&sem4);
}
void worker_thread4(void)
{
sem_wait(&sem4);
do_work();
printf("%lx\n", cnt);
exit(0);
}
int main()
{
pthread_t id1 ,id2, id3, id4;
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
sem_init(&sem4, 0, 0);
pthread_create(&id1, NULL, (void *)worker_thread1, NULL);
pthread_create(&id2, NULL, (void *)worker_thread2, NULL);
pthread_create(&id3, NULL, (void *)worker_thread3, NULL);
pthread_create(&id4, NULL, (void *)worker_thread4, NULL);
sem_post(&sem1);
getchar();
return 0;
}
次に、より一般的なアプローチ、つまりロックスキームについて説明します。
#include <stdio.h>
#include <pthread.h>
pthread_spinlock_t spinlock;
unsigned long cnt = 0;
#define TARGET 0xffffff
void do_work()
{
int i;
for(i = 0; i < TARGET; i++) {
pthread_spin_lock(&spinlock);
cnt ++;
pthread_spin_unlock(&spinlock);
}
if (cnt == 4*TARGET) {
printf("%lx\n", cnt);
exit(0);
}
}
void worker_thread1(void)
{
do_work();
}
void worker_thread2(void)
{
do_work();
}
void worker_thread3(void)
{
do_work();
}
void worker_thread4(void)
{
do_work();
}
int main()
{
pthread_t id1 ,id2, id3, id4;
pthread_spin_init(&spinlock, 0);
pthread_create(&id1, NULL, (void *)worker_thread1, NULL);
pthread_create(&id2, NULL, (void *)worker_thread2, NULL);
pthread_create(&id3, NULL, (void *)worker_thread3, NULL);
pthread_create(&id4, NULL, (void *)worker_thread4, NULL);
getchar();
}
次に、2つの効率の違いを比較します。
[root@localhost linux]# time ./pv
3fffffc
real 0m0.171s
user 0m0.165s
sys 0m0.005s
[root@localhost linux]# time ./spin
3fffffc
real 0m4.852s
user 0m19.097s
sys 0m0.035s
あなたの直感に反して、あなたは最初の例がシリアル操作に退化すると思うかもしれませんか?マルチプロセッサの利点は活用されませんか?2つ目は、マルチスレッドプログラミングの正しい姿勢です。
実際、共有変数の場合は、とにかくシリアルにアクセスする必要があります。この種のコードは、マルチスレッド化することはできません。したがって、実際のマルチスレッドプログラミング:
- 共有変数は必ず削除してください。
- 変数を共有する必要がある場合は、ロックを取得して同時実行性を制御するのではなく、アクセスタイミングを厳密に制御する必要があります。
次に、Linuxカーネルを見てみましょう。多数のスピンロックは、実際にはカーネルをマルチスレッド化するわけではありませんが、純粋に「スピンロックを導入しないと、問題が発生します...」という目的のためです。
RSS、percpuスピンロックはそれを処理する正しい方法のようですが、共有変数の混乱にくしゃくしゃにされたLinuxカーネルをシリアル化するのは簡単ではないようです。さらに、割り込みはそれらのタイミングを制御できません。スレッド割り込み処理はどうですか?効果があまり良くないようです。
同時実行効率の問題の場合、強力なロックを設計すると、実際には問題は認められますが、問題を解決したくありません。これは否定的な応答です。
ロック、すべての悪の源。共有変数をキャンセルしたり、タイミングを制御したりするのは真実です。
それで、違いは何ですか?違いはただのスーツです。
浙江文州の革靴は濡れているので、雨でも太りません。