歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> linux多線程編程——同步與互斥

linux多線程編程——同步與互斥

日期:2017/3/3 11:49:57   编辑:Linux技術
一、 為什麼要用多線程技術?
1、避免阻塞,大家知道,單個進程只有一個主線程,當主線程阻塞的時候,整個進程也就阻塞了,無法再去做其它的一些功能了。
2、避免CPU空轉,應用程序經常會涉及到RPC,數據庫訪問,磁盤IO等操作,這些操作的速度比CPU慢很多,而在等待這些響應時,CPU卻不能去處理新的請求,導致這種單線程的應用程序性能很差。
3、提升效率,一個進程要獨立擁有4GB的虛擬地址空間,而多個線程可以共享同一地址空間,線程的切換比進程的切換要快得多。
二、 如何使用多線程技術進行編程?
下面給出個多線程程序,一個最簡單的模擬售票系統,代碼如下:
[cpp] view
plain copy
#include <stdio.h>
#include <pthread.h>
void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
int main()
{
pthread_t id1,id2;
int error;
error = pthread_create(&id1, NULL, ticketsell1, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
error = pthread_create(&id2, NULL, ticketsell2, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
void *ticketsell1(void *arg)
{
while(1)
{
if(tickets > 0)
{
// usleep(1000);
printf("ticketse1 sells ticket:%d\n",tickets--);
}
else
{
break;
}
}
return (void *)0;
}
void *ticketsell2(void *arg)
{
while(1)
{
if(tickets > 0)
{
// usleep(1000);
printf("ticketse2 sells ticket:%d\n",tickets--);
}
else
{
break;
}
}
return (void *)0;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/mthread$ ./mthread1
ticketse2 sells ticket:20
ticketse2 sells ticket:19
ticketse2 sells ticket:18
ticketse2 sells ticket:17
ticketse2 sells ticket:16
ticketse2 sells ticket:15
ticketse2 sells ticket:14
ticketse2 sells ticket:13
ticketse2 sells ticket:12
ticketse2 sells ticket:11
ticketse2 sells ticket:10
ticketse2 sells ticket:9
ticketse2 sells ticket:8
ticketse2 sells ticket:7
ticketse2 sells ticket:6
ticketse2 sells ticket:4
ticketse2 sells ticket:3
ticketse2 sells ticket:2
ticketse2 sells ticket:1
ticketse1 sells ticket:5
看到結果,我們發現時能正常賣票的,一部分連續是sel2,另一部分是ticketsel1;
此時,其實存在一個隱含的問題,就是線程間的切換,在單CPU系統中,CPU是有時間片時間,時間片到了,就要執行其它的線程,假設thread1執行到if裡面,但在printf執行前發生了線程切換,那麼會發生什麼呢?我們在這裡用usleep函數(放開程序中的usleep注釋行)進行強制模擬切換;
我們看看結果:
[cpp] view
plain copy
fs@ubuntu:~/qiang/mthread$ gcc -o mthread1 mthread1.c -lpthread
fs@ubuntu:~/qiang/mthread$ ./mthread1
ticketse2 sells ticket:20
ticketse1 sells ticket:19
ticketse2 sells ticket:18
ticketse1 sells ticket:17
ticketse2 sells ticket:16
ticketse1 sells ticket:15
ticketse2 sells ticket:14
ticketse1 sells ticket:13
ticketse2 sells ticket:12
ticketse1 sells ticket:11
ticketse2 sells ticket:10
ticketse1 sells ticket:9
ticketse2 sells ticket:8
ticketse1 sells ticket:7
ticketse2 sells ticket:6
ticketse1 sells ticket:5
ticketse2 sells ticket:4
ticketse1 sells ticket:3
ticketse1 sells ticket:2
ticketse2 sells ticket:1
ticketse1 sells ticket:0
fs@ubuntu:~/qiang/mthread$
運行程序發現竟然有0號票被賣出了,這顯然是錯誤的!當thread1的if裡面發生線程切換時,thread2得到運行,把最後一張票賣了,此時thread1恢復運行,結果賣出了0號票,這裡我們需要的是火車票的票數數據對於所有線程而言是同步的,所以就要用到線程同步技術了。
三、 使用多線程的同步與互斥
1、多線程的同步方式有很多種,例如互斥鎖,條件變量,信號量,讀寫鎖。先看看互斥鎖如何解決多線程之間的同步問題。程序用互斥鎖後如下:
[cpp] view
plain copy
#include <stdio.h>
#include <pthread.h>
void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
pthread_mutex_t mutex;
int main()
{
pthread_t id1,id2;
pthread_mutex_init(&mutex, NULL);//
int error;
error = pthread_create(&id1, NULL, ticketsell1, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
error = pthread_create(&id2, NULL, ticketsell2, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
void *ticketsell1(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);//給互斥量上鎖
if(tickets > 0)
{
usleep(1000);
printf("ticketse1 sells ticket:%d\n",tickets--);
pthread_mutex_unlock(&mutex);//給互斥量解鎖
}
else
{
pthread_mutex_unlock(&mutex);//給互斥量解鎖
break;
}
pthread_yield();//線程調度函數,使每個線程都有執行機會
}
return (void *)0;
}
void *ticketsell2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);//給互斥量上鎖
if(tickets > 0)
{
usleep(1000);
printf("ticketse2 sells ticket:%d\n",tickets--);
pthread_mutex_unlock(&mutex);//給互斥量解鎖
}
else
{
pthread_mutex_unlock(&mutex);//給互斥量解鎖
break;
}
pthread_yield();//線程調度函數,是兩個線程都有執行機會
}
return (void *)0;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/mthread$ vi mthread1.c
fs@ubuntu:~/qiang/mthread$ gcc -o mthread1 mthread1.c -lpthread
fs@ubuntu:~/qiang/mthread$ ./mthread1
ticketse2 sells ticket:20
ticketse1 sells ticket:19
ticketse2 sells ticket:18
ticketse1 sells ticket:17
ticketse2 sells ticket:16
ticketse1 sells ticket:15
ticketse2 sells ticket:14
ticketse1 sells ticket:13
ticketse2 sells ticket:12
ticketse1 sells ticket:11
ticketse2 sells ticket:10
ticketse1 sells ticket:9
ticketse2 sells ticket:8
ticketse1 sells ticket:7
ticketse2 sells ticket:6
ticketse1 sells ticket:5
ticketse2 sells ticket:4
ticketse1 sells ticket:3
ticketse2 sells ticket:2
ticketse1 sells ticket:1
2、再看看用信號量來解決多線程的同步問題,程序代碼如下:
[cpp] view
plain copy
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
sem_t mutex,full;
int main()
{
pthread_t id1,id2;
int error;
int ret;
ret = sem_init(&mutex, 0 ,1);//初始化mutex信號量為1
ret += sem_init(&full, 0 ,0);//初始化full信號量為0
if(ret != 0)
{
printf("sem_init fails!\n");
}
error = pthread_create(&id1, NULL, ticketsell1, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
error = pthread_create(&id2, NULL, ticketsell2, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
void *ticketsell1(void *arg)
{
while(1)
{
sem_wait(&mutex);//mutex信號量進行P操作
if(tickets > 0)
{
usleep(1000);
printf("ticketse1 sells ticket:%d\n",tickets--);
sem_post(&full);//full信號量進行V操作
}
else
{
sem_post(&full);//full信號量進行V操作
break;
}
}
return (void *)0;
}
void *ticketsell2(void *arg)
{
while(1)
{
sem_wait(&full);//full信號量進行P操作
if(tickets > 0)
{
usleep(1000);
printf("ticketse2 sells ticket:%d\n",tickets--);
sem_post(&mutex);//mutex信號量進行V操作
}
else
{
sem_post(&mutex);//mutex信號量進行V操作
break;
}
}
return (void *)0;
}
執行結果:
[cpp] view
plain copy
fs@ubuntu:~/qiang/mthread$ vi mthread1.c
fs@ubuntu:~/qiang/mthread$ gcc -o mthread1 mthread1.c -lpthread
fs@ubuntu:~/qiang/mthread$ ./mthread1
ticketse1 sells ticket:20
ticketse2 sells ticket:19
ticketse1 sells ticket:18
ticketse2 sells ticket:17
ticketse1 sells ticket:16
ticketse2 sells ticket:15
ticketse1 sells ticket:14
ticketse2 sells ticket:13
ticketse1 sells ticket:12
ticketse2 sells ticket:11
ticketse1 sells ticket:10
ticketse2 sells ticket:9
ticketse1 sells ticket:8
ticketse2 sells ticket:7
ticketse1 sells ticket:6
ticketse2 sells ticket:5
ticketse1 sells ticket:4
ticketse2 sells ticket:3
ticketse1 sells ticket:2
ticketse2 sells ticket:1
fs@ubuntu:~/qiang/mthread$
上面的sem_init函數用來初始化兩個信號量的初始化值,這裡一個設為1,一個設為0,sem_wait類似於P操作,讓信號量減1,如果小於結果小於0,線程阻塞,否則線程繼續執行,sem_post類似於V操作,提升信號量的值,加1,通過這兩個信號量之間的互相“救對方”,就可以實現這兩個線程的同步執行。
我們編譯運行以上程序,發現兩個售票點交替賣票,兩個純程依次得到機會執行,並且不會有0號票賣出,實現了同步。
3、我們再用條件變量來解決同步問題,一般條件變量需要結合互斥量一起使用,代碼如下
[cpp] view
plain copy
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
pthread_mutex_t mutex;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;//靜態初始化條件變量;
int main()
{
pthread_t id1,id2;
pthread_mutex_init(&mutex, NULL);
int error;
error = pthread_create(&id1, NULL, ticketsell1, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
error = pthread_create(&id2, NULL, ticketsell2, NULL);
if(error != 0)
{
printf("pthread is not created!\n");
return -1;
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
void *ticketsell1(void *arg)
{
pthread_mutex_lock(&mutex);
while(tickets > 0)
{
if(tickets%2 == 1)
{
usleep(1000);
printf("ticketse1 sells ticket:%d\n",tickets--);
pthread_cond_signal(&qready);//條件改變,發送信號,通知ticketse2
}
else
{
pthread_cond_wait(&qready,&mutex);//解開Mutex,並等待qready改變
}
pthread_mutex_unlock(&mutex);//給互斥量解鎖
}
return (void *)0;
}
void *ticketsell2(void *arg)
{
pthread_mutex_lock(&mutex);
while(tickets > 0)
{
if(tickets%2 == 0)
{
usleep(1000);
printf("ticketse2 sells ticket:%d\n",tickets--);
pthread_cond_signal(&qready);//條件改變,發送信號,通知ticketse1
}
else
{
pthread_cond_wait(&qready,&mutex);//解開mutex,並等待qready改變
}
pthread_mutex_unlock(&mutex);//給互斥量解鎖
}
return (void *)0;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/mthread$ vi mthread1.c
fs@ubuntu:~/qiang/mthread$ gcc -o mthread1 mthread1.c -lpthread
fs@ubuntu:~/qiang/mthread$ ./mthread1
ticketse2 sells ticket:20
ticketse1 sells ticket:19
ticketse2 sells ticket:18
ticketse1 sells ticket:17
ticketse2 sells ticket:16
ticketse1 sells ticket:15
ticketse2 sells ticket:14
ticketse1 sells ticket:13
ticketse2 sells ticket:12
ticketse1 sells ticket:11
ticketse2 sells ticket:10
ticketse1 sells ticket:9
ticketse2 sells ticket:8
ticketse1 sells ticket:7
ticketse2 sells ticket:6
ticketse1 sells ticket:5
ticketse2 sells ticket:4
ticketse1 sells ticket:3
ticketse2 sells ticket:2
ticketse1 sells ticket:1
fs@ubuntu:~/qiang/mthread$
條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件變量發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步.
函數pthread_cond_wait使線程阻塞在一個條件變量上,而函數pthread_cond_signal是用來釋放被阻塞在條件變量上的一個線程。但是要注意的是,條件變量只是起到阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,我這裡給出的是tickets是否是偶數這個條件。
Copyright © Linux教程網 All Rights Reserved