一、線程的基本概念 及 建立
1、線程的概念(了解)
— 程序的關系
- 程序是 運作一段程式 系統給其配置設定資源的總稱;
- 程序有獨立的位址空間;每個程序互不影響,隻能通路自己空間的資料;
- Linux為每個程序建立task_struct 結構體;
- 每個程序都參與核心排程,互不影響;
— 、為啥需要線程,線程的作用
- 程序再切換時對系統的開銷很大;
以上如圖:
- 程序(P1、P2)建立後 系統将資料放入記憶體(RAM)中,CPU去執行某一個程序時,需要去從程序中擷取指令,要去取址、譯碼、執行,還要去通路資料,這些東西都是放在記憶體中的,cpu需要去記憶體中擷取指令或資料區執行,處理。cpu頻率很快,相對向記憶體讀取要快的多,中間加一個Cache(高速緩存:靜态RAM),每次運作程序前,将程序的部分資料放入cache中,cpu直接在cache中讀取資料。
- 當程序間切換時,cache需要重新整理,還有一種 tlb(頁表緩存),每個程序的實體位址是不一樣的。
- 很多作業系統引入輕量級程序LWP(線程)。
- 同一程序中的線程共享相同位址空間。
- Linux中不區分 程序、線程。
2、線程特點(了解)
- 通常線程指的是共享相同位址空間的多個任務。
- 使用多線程的好處:
- 大大提高了任務切換的效率。
- 避免了額外的TLB&cache的重新整理。
一個程序中。
- 一個程序中的多個線程共享以下資源。
線程ID(TID)
PC(程式計數器)和相關寄存器。
堆棧
錯誤号(errno);
優先級;
執行狀态和屬性;
- pthread線程庫中提供了如下基本操作。
建立線程;
回收線程;
結束線程;
- 同步和互斥機制
信号量;
互斥鎖;
4、線程的建立(熟悉)
— 線程建立 — pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread , const pthread_attr_t *attr , void*(routine)(void*), void *arg);
- 成功傳回0,失敗傳回錯誤碼;
- thread 線程對象;
- attr :線程屬性,NULL代表預設屬性;
- routine : 線程執行的函數;
- arg : 傳遞給routine 的參數;
— 線程回收 — pthread_join
#include <pthread.h>
int pathread_join(pthread_t thread , void ** retval);
- 成功傳回0,失敗時傳回錯誤碼;
- thread 要回收的線程對象;
- 調用線程阻塞直到thread結束;
- *retval 接收線程thread的傳回值;(一個二級指針傳回的是一個位址)
— 結束一個線程 — pthread_exit
#include <pthread.h>
void pthread_exit(void*retval);
- 結束目前程序;
- retval可被其他線程通過pthread_join擷取。(傳回的retval位址不能是一個局部變量的位址,不然該空間被釋放,會出錯)
示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char message[32] = "Hello World";
void *thread_func(void *arg);
int main(int argc, const char *argv[])
{
pthread_t a_thread;
void *result;
if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0)
{
printf("fail to pthread_create");
exit(-1);
}
pthread_join(a_thread,&result); //線程回收
printf("result is %s \n",(char*)result);
printf("message is %s \n",message);
return 0;
}
void *thread_func(void *arg)
{
sleep(1); //
strcpy(message,"markey by thread");
pthread_exit("thank you for waiting for me"); //線程退出
}
- 線程時通過第三方的庫函數實作的,在編譯時要加入相應的庫。
- 之前講過 多程序中 父程序結束時,子程序還會再運作;但在多線程中:當程序中的主線程man傳回結束時,還程序中的所有線程都會結束。
二、線程兼通信的問題
1、同步機制(了解)
- 線程共享同一程序的位址空間;
- 優點:線程間通信很容易
- 通過全局變量交換資料;
- 缺點:多個線程通路共享資料時需要同步或互斥機制。
2、線程通信 — 同步
- 同步(synchronization)指的是多個任務按照約定的先後次序互相配合完成一件事。(後完成的任務通常都需要等待 先完成的任務完成,先完成的任務完成後通知後任務)
- 1968年,Edsgar Dijktra基于信号量的概念 提出了一種 同步進制。
- 由信号量來決定線程是繼續運作還是 阻塞等待;
3、信号量(了解)
- 信号量(燈):信号量代表某一類資源,其值表示系統中該資源的數量(沒資源為0,有資源大于0)
- 信号量是一個受保護的變量,智能通過三種操作來通路。
- 初始化;
- P操作(申請資源):檢查信号量的值是否大于0,大于0有資源,任務繼續執行,去通路;等于0時沒資源,線程阻塞,等待該資源。
- V操作(釋放資源): 任務不需要通路資源,或者任務産生了一個資源,告訴其他需要該資源的任務。
4、對信号量基本操之 — P操作(熟悉)
- 信号量 --- P
P(S)含義如下:
if(信号量的值大于0)
{
申請資源的任務繼續運作;
信号量的值減一;
}else
{
申請資源的任務阻塞;
}
5、對信号量的 V操作(熟悉)
- 信号量 ------ V
- V(S)含義如下:
信号量的值加一;
if(有任務在等待資源)
{
喚醒等待的任務,讓其繼續運作;
}
6、對應相應函數
— Posix信号量
- posix中定義了兩種信号量;
- 無名信号量(基于記憶體的信号量):主要用于程序内部,線程之間的通信(同步);
- 有名信号量(既可以用于程序之間,也可以用于線程之間);
- pthread庫常用的信号量操作函數如下:
int sem_init(sem_t *sem , int pshared , unsigned int value);
int sem_wait(sem_t *sem); P操作
int sem_post(sem_t *sem); V操作
2、 信号量初始化 -- sem_init
- #include <semaphore.h>
- int sem_init(sem_t *sem , int pshared , unsigned int value);
- 成功時傳回0,失敗時傳回 EOF;
- sem 指向要初始化的信号量對象;
- pashared 0 — 線程間使用, 1— 程序間使用;
- val : 信号量初值;
3、信号量操作 — P/V操作
- #include <semaphore.h>
- int sem_wait(sem_t *sem); P操作
- int sem_post(sem_t *sem); V操作
- 成功時傳回0,失敗時傳回EOF;
- sem指向要操作的信号量對象;
三、信号量同步示例1、2
1、信号量同步示例1 (熟練)
- 兩個線程同步讀寫緩沖區(生産者/消費者問題)
— 寫緩沖區線程 : writer 讀緩沖區線程 : reader
— 寫緩沖區對信号量進行一個P操作,并喚醒 讀程序、都程序喚醒後對信号量進行一個V操作。
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char buf[32];
sem_t sem;
void *function(void *arg);
int main(int argc, const char *argv[])
{
pthread_t a_thread;
if(sem_init(&sem,0,0) < 0)
{
perror("sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL) != 0)
{
perror("pthread_create");
exit(-1);
}
do
{
fgets(buf,32,stdin); //按行标準輸入
sem_post(&sem); //對信号量進行V操作,擷取/釋放資源
}while(strncmp(buf,"quit",4) != 0);
return 0;
}
void *function(void *arg)
{
while(1)
{
sem_wait(&sem);//對信号量進行P操作,申請資源
printf("you enter %d characters \n",strlen(buf));
}
}
— ps aux –L |grep sem
2、信号量同步示例2 (熟練)
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char buf[32];
sem_t sem_r;
sem_t sem_w;
void *function(void *arg);
int main(int argc, const char *argv[])
{
pthread_t a_thread;
if(sem_init(&sem_r,0,0) < 0)
{
perror("sem_init");
exit(-1);
}
if(sem_init(&sem_w,0,1) < 0)
{
perror("sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL) != 0)
{
perror("pthread_create");
exit(-1);
}
do
{
sem_wait(&sem_w);
fgets(buf,32,stdin);
sem_post(&sem_r); //對信号量進行V操作,擷取/釋放資源
}while(strncmp(buf,"quit",4) != 0);
return 0;
}
void *function(void *arg)
{
while(1)
{
sem_wait(&sem_r);//對信号量進行P操作,申請資源
printf("you enter %d characters \n",strlen(buf));
sem_post(&sem_w);
}
}
注:任何信号量都要進行初始化。
小結: sem_init
sem_wait
sem_post
四、程序間的互斥
1、臨界資源(了解)
- 臨界資源
- 通常是一個共享資源;
- 一次隻能允許被一個任務(程序、線程)通路;
- 臨界區
- 通路臨界區的代碼;(其他代碼成為非臨界代碼)
- 互斥機制
- mutex互斥鎖;
- 要不沒有任務持有該鎖,要不隻有一個任務隻有該鎖。
- 任務通路臨界資源前申請鎖,通路完後釋放鎖;
2、互斥機制
- 互斥鎖(熟悉)
- 互斥鎖初始化 — pthread_mutex_init
#include <pthread.h>
int pathread_mutex_init(pathread_mutex_t *mutex , const pthread_mutexattr_t * attr);
- 成功時傳回0,失敗時傳回錯誤碼;
- mutex 指向要初始化 的互斥 鎖對象;
- attr : 互斥鎖的屬性,NULL表示預設屬性;
- 申請鎖 — pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t * mutex);
- 成功時傳回0,失敗時傳回錯誤碼;
- mutex 指向要 初始化的互斥鎖對象;
- 如果無法獲得鎖,任務阻塞;
- 釋放鎖 — pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t * mutex);
- 成功時傳回0,失敗時傳回錯誤碼;
- mutex 指向要 初始化的互斥鎖對象;
- 執行完臨界區要及時釋放鎖;
示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
unsigned int count,value1,value2;
pthread_mutex_t lock;
void *thread_func(void *arg);
int main(int argc, const char *argv[])
{
pthread_t a_thread;
if(pthread_mutex_init(&lock,NULL) != 0)
{
printf("fail to pthread_mutex_init \n");
exit(-1);
}
if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0)
{
printf("fail to pthread_create");
exit(-1);
}
while(1)
{
count++;
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return 0;
}
void *thread_func(void *arg)
{
while(1)
{
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
if(value1 != value2)
{
printf("value1 =%u , value2 = %u \n",value1,value2);
usleep(100000);//将線程挂起一段時間,機關微妙
}
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
//printf("value1 =%u , value2 = %u \n",value1,value2);
}
return NULL;
}
注:
不使用互斥鎖運作:$gcc -o test test.c -Wall -lpthread
$./test
value1 = 198 , value2 = 199;
value1 = 2398 , value2 = 2399;
使用互斥鎖運作:$gcc -o test test.c -Wall -lpthread _D_LOCK_
$./test
總結: (1) 程序與 線程量程在時間片的作用下 互動執行值,count的值一直在疊加的狀态;
(2)在沒有使用互斥鎖的時候,程序中再給value1或者value2指派的時候可能會遇到時間片使用完的情況,會有當次某 一個指派失敗的情況,導緻跳入線程中後判定兩值不相等,列印出對應數值;
(3)加了互斥鎖以後,在申請鎖成功以後,在跳入另一個程序中時,另一個程序也在申請鎖時,會失敗進入阻塞狀态, 又會傳回擁有鎖的程序繼續執行,直到程序釋放鎖,另一個程序得到執行,這是不是出現(2)中的情況;