這是個老掉牙的話題,但基本上絕大多數的討論都跑偏了,
絕大多數討論的核心在于 如何設計一把鎖來同步共享變數的訪問, 這事實上完全是本末倒置:
- 我們需要設計的一個立交橋,而不是為了設計一個紅綠燈!
事實上,多執行緒編程就不應該訪問共享變數,如果真的要在多執行緒訪問共享變數,唯一高效的方案就是 嚴格控制時序, 嗯,先來后到是唯一的方法,至于說設計這樣那樣的鎖,那完全是惰政,只是為了防止出問題而已,
早在100多年前,就可以在同一根電話線上傳輸不同的話路,這得益于嚴格的時隙分配和復用機制,后來時代進步了,事情反而變得糟糕了,這完全是由于另一種時隙復用方式引起的,那便是時隙統計復用,現代作業系統和現代分組交換網是這種復用方式的忠實踐行者,
我并不認為統計復用是一種高效的方式,它也許只是面對多樣化場景而不得不采用的一種方案,在我看來,若單單討論高效,沒有什么比嚴格的時隙復用更好的了,
我來舉一個例子,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();
}
現在比較一下二者的效率差異:
[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
和直覺相反,也許你會覺得,第一個例子那不就是退化成串行操作了嗎?多處理器的優勢豈不是無法發揮了?第二個才是多執行緒編程正確的姿勢啊!
事實上,對于共享變數,無論如何它都是必須要串行訪問的,這種代碼根本就無法多執行緒化,所以,真正的多執行緒程式設計:
- 一定要消除共享變數,
- 若非要共享變數,那就一定嚴格控制訪問時序,而不是靠搶鎖來控制并發,
現在來看看Linux內核,大量的spinlock并不是真的讓內核多執行緒化了,而純粹是為了 “如果不引入spinlock就會出問題…”
RSS,percpu spinlock似乎是正確的把式,但若要將已經被揉成亂麻的Linux內核徹底抽絲剝繭般的共享變數串行化,似乎并不容易,更何況,中斷是無法控制其時序的,那么中斷處理執行緒化如何呢?似乎效果也不是很好,
遇到并發效率問題,如果你去設計一把牛逼的鎖,那事實上你是承認了問題的所在但并不想去解決問題,這是一種消極的應對,
鎖,萬惡之源,取消共享變數或者控制時序才是真理,
那么,差別是什么?差別只是一套西服,
浙江溫州皮鞋濕,下雨進水不會胖,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/157358.html
標籤:其他
上一篇:Fire
