歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> Linux 系統應用編程——線程基礎

Linux 系統應用編程——線程基礎

日期:2017/3/3 11:49:58   编辑:Linux技術
傳送門: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$
我們可以看到兩個線程是同步的。
Copyright © Linux教程網 All Rights Reserved