傳送門:Linux多線程編程實例解析 .
linux多線程編程——同步與互斥 .
傳統多任務操作系統中一個可以獨立調度的任務(或稱之為順序執行流)是一個進程。每個程序加載到內存後只可以唯一地對應創建一個順序執行流,即傳統意義的進程。每個進程的全部系統資源是私有的,如虛擬地址空間,文件描述符和信號處理等等。使用多進程實現多任務應用時存在如下問題:
1)任務切換,即進程間上下文切換,系統開銷比較大。(虛擬地址空間以及task_struct 都需要切換)
2)多任務之間的協作比較麻煩,涉及進程間通訊。(因為不同的進程工作在不同的地址空間)
所以,為了提高系統的性能,許多操作系統規范裡引入了
輕量級進程的概念,也被稱為
線程。
一、線程基礎 通常線程指的是共享相同地址空間的多個任務。線程最大的特點就是
在同一個進程中創建的線程共享該進程的地址空間;但
一個線程仍用task_struct 來描述,線程和進程都參與統一的調度。所以,多線程的好處便體現出來:
1)
大大提高了任務切換的效率;因為各線程共享進程的地址空間,任務切換時只要切換task_struct 即可;
2)
線程間通信比較方便;因為在同一塊地址空間,數據共享;
當然,共享地址空間也會成為線程的缺點,因為共享地址空間,如果其中一個線程出現錯誤(比如段錯誤),整個線程組都會崩掉!
Linux之所以稱呼其線程為LWP( Light Weight Process ),因為從內核實現的角度來說,它並沒有為線程單獨創建一個結構,而是繼承了很多進程的設計:
1)繼承了進程的結構體定義task_struct ;
2)沒有專門定義線程ID,復用了PID;
3)更沒有為線程定義特別的調度算法,而是沿用了原來對task_struct 的調度算法。
在
最新的Linux內核裡線程已經替代原來的進程稱為調度的實際最小單位。
原來的進程概念可以看成是多個線程的容器,稱之為
線程組;即
一個進程就是所有相關的線程構成的一個線程組。傳統的進程等價於單線程進程。
每個線程組都有自己的標識符 tgid (數據類型為 pid_t ),其值等於該進程(線程組)中的第一個線程(group_leader)的PID。
1、創建線程 pthread_create()函數描述如下:
所需頭文件#include <pthread.h>函數原型int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
void *(* routine)(void *), void *arg)
函數參數thread :創建的線程
attr :指定線程的屬性,NULL表示使用缺省屬性
routine :線程執行的函數
arg :傳遞給線程執行的函數的參數
函數返回值成功: 0
出錯: -1
1)這裡routine 是
回調函數(callback),其
函數類型由內核來決定,這裡我們將其
地址傳給內核;這個函數並不是線程創建了就會執行,而是只有
當其被調度到cpu上時才會被執行;具體回調函數的講解,移步Linux
C 函數指針應用---回調函數 .;
2)arg 是線程執行函數的參數,這裡我們將其地址穿進去,使用時需要先進行類型轉換,才能使用;如果參數不止一個,我們可以將其放入到結構體中;
2、pthread_join () 函數其函數描述如下:
所需頭文件#include <pthread.h>函數原型int thread_join(pthread_t thread, void ** value_ptr)函數參數thread :要等待的線程
value_ptr :指針 *value_ptr 指向線程返回的參數
函數返回值成功: 0
出錯: -1
這裡,我們可以看到 value_ptr 是個二級指針,其是出參,存放的是線程返回參數的地址;
3、pthread_exit 函數其函數描述如下:
所需頭文件#include <pthread.h>函數原型int pthread_exit(void *value_ptr)函數參數value_ptr :線程退出時返回的值函數返回值成功:0
出錯:-1
和進程中的exit() 、wait()一樣,這裡pthread_join 與 pthread_exit 是工作在兩個線程之中;
下面看一個實例:
[cpp] view
plain copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
char message[32] = "Hello World!";
void *thread_function(void *arg);
int main()
{
pthread_t a_thread;
void *thread_result;
if(pthread_create(&a_thread,NULL,thread_function,(void *)message) < 0)
{
perror("fail to pthread_create");
exit(-1);
}
printf("waiting for thread to finish\n");
if(pthread_join(a_thread,&thread_result) < 0)
{
perror("fail to pthread_join");
exit(-1);
}
printf("Message is now %s\n",message);
printf("thread_result is %s\n",(char *)thread_result);
return 0;
}
void *thread_function(void *arg)
{
printf("thread_function is running,argument is %s\n",(char *)arg);
strcpy(message,"marked by thread");
pthread_exit("Thank you for the cpu time");
}
編譯
[cpp] view
plain copy
fs@ubuntu:~/qiang/thread/0107$ gcc -o thread thread.c -lpthread
fs@ubuntu:~/qiang/thread/0107$
線程通過第三方的線程庫來實現,所以這裡要 -lpthread ,-l 是鏈接一個庫,這個庫是pthread;
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/thread/0107$ ./thread
waiting for thread to finish
thread_function is running,argument is Hello World!
Message is now marked by thread
thread_result is Thank you for the cpu time
fs@ubuntu:~/qiang/thread/0107$
從這個程序,我們可以看到線程之間是如何通信的,線程之間通過二級指針來傳送參數的地址(這是進程所不具備的,因為他們的地址空間獨立),但兩個線程之間的通信,傳遞的
數據的生命周期必須是靜態的。可以使全局變量、static修飾的數據、堆裡面的數據;這個程序中的message就是一個全局變量。其中一個線程可以修改它,另一個線程得到它修改過後的message。
二、線程的同步和互斥先來了解同步和互斥的基本概念:
臨界資源:某些資源來說,其在同一時間只能被一段機器指令序列所占用。這些一次只能被一段指令序列所占用的資源就是所謂的
臨界資源。臨界區:對於臨界資源的訪問,必須是互斥進行。也就是當臨界資源被一個指令序列占用時,另一個需要訪問相同臨界資源的指令序列就不能被執行。指令序列不能執行的實際意思就是其所在的進程/線程會被阻塞。所以我們定義
程序內訪問臨界資源的代碼序列被稱為臨界區。互斥:是指同事只允許一個訪問者對臨界資源進行訪問,具有
唯一性和
排它性。但互斥無法限制訪問這個對資源的訪問順序,即訪問時
無序的。
同步:是指在
互斥的基礎上,通過其他機制實現訪問者對資源的
有序訪問。
1、線程間互斥引入
互斥(mutual exlusion)鎖的目的是用來
保證共享數據的完整性。互斥鎖主要用來
保護臨界資源。每個臨界資源都有一個互斥鎖來保護,任何時刻最多只能有一個線程能訪問該資源;線程必須先獲得互斥鎖才能訪問臨界資源,訪問完資源後釋放該鎖。如果無法獲得鎖,線程會阻塞直到獲得鎖為止;
通常,我們在
臨界區前上鎖,臨界區後解鎖;
1)初始化互斥鎖函數所需頭文件#include <pthread.h>函數原型int pthread_mutex_init (pthread_mutex_t *mutex, pthread_mutexattr_t *attr )
//初始化互斥鎖
函數參數mutex:互斥鎖
attr :互斥鎖屬性 // NULL表示缺省屬性
函數返回值成功:0
出錯:-1
2)申請互斥鎖函數所需頭文件#include <pthread.h>函數原型int pthread_mutex_lock(pthread_mutex_t *mutex)
//申請互斥鎖
函數參數mutex:互斥鎖
函數返回值成功:0
出錯:-1
3)釋放互斥鎖函數所需頭文件#include <pthread.h>函數原型int pthread_mutex_unlock(pthread_mutex_t *mutex)
//釋放互斥鎖
函數參數mutex:互斥鎖
函數返回值成功:0
出錯:-1
下面是一個實例:
[cpp] view
plain copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
//#define _LOCK_
unsigned int value1,value2,count;
pthread_mutex_t mutex;
void *function(void *arg);
int main()
{
pthread_t a_thread;
if(pthread_mutex_init(&mutex,NULL) < 0)
{
perror("fail to mutex_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL) != 0)
{
perror("fail to pthread_create");
exit(-1);
}
while(1)
{
count++;
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return 0;
}
void *function(void *arg)
{
while(1)
{
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
if(value1 != value2)
{
printf("count = %d,value1 = %d,value2 = %d\n",count,value1,value2);
usleep(100000);
}
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return NULL;
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/thread/0107$ ./mutex
count = 3368408,value1 = 3368408,value2 = 3368407
count = 44174760,value1 = 44174760,value2 = 44174759
count = 69313865,value1 = 69313865,value2 = 69313864
count = 139035309,value1 = 139035309,value2 = 139035308
count = 168803956,value1 = 168803956,value2 = 168803955
count = 192992611,value1 = 192992611,value2 = 192992610
count = 224279903,value1 = 224279903,value2 = 224279902
count = 259586793,value1 = 259586793,value2 = 259586792
count = 282057307,value1 = 282057307,value2 = 282057306
count = 321607823,value1 = 321607823,value2 = 321607822
count = 351629940,value1 = 351629940,value2 = 351629939
count = 374130545,value1 = 374130545,value2 = 374130544
count = 400727525,value1 = 400727525,value2 = 400727524
count = 440219988,value1 = 440219988,value2 = 440219987
count = 466069865,value1 = 466069865,value2 = 466069864
count = 500581241,value1 = 500581241,value2 = 500581240
count = 522649671,value1 = 522649671,value2 = 522649670
count = 569234325,value1 = 569234325,value2 = 569234324
count = 608139152,value1 = 608139152,value2 = 608139151
count = 639493957,value1 = 639493957,value2 = 639493956
.....
我們可以看到,數據是不斷被打印的,說明 a 線程是可以訪問臨界資源的。
我們把#define _LOCK_前面的注釋去掉,這時就加上了互斥鎖,執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/thread/0107$ ./mutex
此時,並沒有數據被打印,說明此時a線程中 value1 與 value 2 一直是相等的,說明主線程執行是,a線程並無法訪問臨界資源的。
2、線程間同步同步(synchronization) 指的是多個任務(線程)按照約定的順序相互配合完成一件事情;
線程間同步——P / V 操作
信號量代表某一類資源,其值表示系統中該資源
當前可用的數量。
信號量是一個受保護的變量,只能通過三種操作來訪問:
1)初始化
2)
P操作(申請資源)
3)
V操作(釋放資源)P(S)含義如下:
[cpp] view
plain copy
if (信號量的值大於0)
{
請資源的任務繼續運行;
信號量的值 減一;
}
else
{
請資源的任務阻塞;
}
V(S)含義如下:
[cpp] view
plain copy
if (沒有任務在等待該資源)
{
信號量的值 加一;
}
else
{
喚醒第一個等待的任務,讓其繼續運行;
}
1)、信號量初始化函數:
所需頭文件#include <semaphore.h>函數原型int sem_int (sem_t *sem,int pshared,unsigned int value)
//初始化信號量
函數參數sem:初始化的信號量
pshared:信號量共享的范圍(0:線程間使用 非0 :進程間使用)
value :信號量初值
函數返回值成功:0
出錯:-1
2)P操作
所需頭文件#include <semaphore.h>函數原型int sem_wait (sem_t *sem) //P操作
函數參數sem:信號量
函數返回值成功:0
出錯:-1
3)V操作所需頭文件#include <semaphore.h>函數原型int sem_post(sem_t *sem) //V操作
函數參數sem:信號量
函數返回值成功:0
出錯:-1
下面是個實例:
[cpp] view
plain copy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
char buf[60];
sem_t sem;
void *function(void *arg);
int main(int argc, char *argv[])
{
pthread_t a_thread;
void *thread_result;
if(sem_init(&sem,0,0) != 0)
{
perror("fail to sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL) != 0)
{
perror("fail to pthread_create");
exit(-1);
}
printf("input 'quit' to exit\n");
do
{
fgets(buf,60,stdin);
sem_post(&sem);
}
while(strncmp(buf,"quit",4) != 0);
return 0;
}
void *function(void *arg)
{
while(1)
{
sem_wait(&sem);
printf("you enter %d characters\n",strlen(buf) - 1);
}
}
執行結果如下:
[cpp] view
plain copy
fs@ubuntu:~/qiang/thread/0107$ ./sem
input 'quit' to exit
xiao
you enter 4 characters
zhi
you enter 3 characters
qiang
you enter 5 characters
quit
fs@ubuntu:~/qiang/thread/0107$
我們可以看到兩個線程是同步的。