3.7.1.再論進程
3.7.1.一、多進程實現同時讀取鍵盤和鼠標
3.7.1.二、使用進程技術的優點
(1)CPU時分複用,單核心CPU能夠實現宏觀上的並行、微觀上的串行
(2)實現多任務系統需求(多任務的需求是客觀的,多任務就是同時要作不少事情)
3.7.1.三、進程技術的劣勢
(1)進程間切換開銷大(進程斷點的保護和進程斷點的恢復)
(2)進程間通訊麻煩並且效率低(進程與進程之間天生是隔離的或者說是獨立的)
3.7.1.四、解決方案就是線程技術
(1)線程技術保留了進程技術實現多任務的特性。
(2)線程的改進就是在線程間切換和線程間通訊上提高了效率。【這個是關鍵點】
(3)多線程在多核心CPU上面更有優點。
什麼是多核心CPU:就是一塊CPU上面有2個運算單元核心就叫作雙核
一個CPU上只有1個CPU核心就叫作單核
有3個的叫作3核
4個的叫作4核
5個就是5核(== 沒見過5核)
可是有6核.....
3.7.2.線程的引入
3.7.2.一、使用線程技術同時讀取鍵盤和鼠標
3.7.2.二、linux中的線程簡介
(1)一種輕量級進程(某種意義上來講進程和線程是類似的,一個進程中包含有多個線程)一個進程裏能夠有多個線程。線程只能經過或者說是依附於進程存在。
(2)線程是參與內核調度的最小單元
(3)一個進程中能夠有多個線程
3.7.2.三、線程技術的優點
(1)像進程同樣可被OS調度
(2)同一進程的多個線程之間很容易高效率通訊(本質上一個進程裏的線程就是一個一個函數,線程間通訊本質就是一個程序(能夠看做是一個進程)之間的函數間通訊,而進程間通訊能夠理解成不一樣程序間的通訊,效率比較低)
(3)在多核心CPU(對稱多處理器架構SMP)架構下效率最大化
多核心CPU能保證多個【線程】在真正意義上的並行,而不能保證多個【進程】的同時運行。
-----------------------------------------------------------------------------------------------------------------------------------------------------------
多線程編程補充:
(1)線程的基本操做包括線程的建立、合併、終止線程以及設置線程的屬性,Linux下的多線程編程接口遵循POSIX標準,稱爲pthread,注意線程使用的一個基本數據結構pthread_t(其實是一個unsigned long int 數據)。
(2)線程的建立函數是:pthread_create(),相似於fork()函數,對這個函數的解析:----------------這個函數寫在主線程中
頭文件
#include<pthread.h>
函數聲明
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
返回值
若成功則返回0,不然返回出錯編號EAGAIN/EINVAL。
第一個參數:是一個指針,指向pthread_t 類型的數據,&id爲前面定義的一個pthread_t 類型對象的地址,以獲取新建線程的標識符;第二個參數用來設置線程的屬性,也是一個指針,指向 pthread_attr_t類型的數據;第三個參數用來肯定線程運行函數的起始地址和傳遞給這個函數的參數。
(3)合併線程:pthread_join()函數:【相似於多進程中的wait函數】----------------這個函數寫在主線程中
pthread_join使一個線程等待另外一個線程結束。子線程代碼中若是沒有pthread_join函數,主線程會很快結束從而使整個進程結束,從而使建立的線程沒有機會開始執行就結束了。加入pthread_join後,主線程會一直阻塞等待直到等待的線程結束本身才結束,使建立的線程有機會執行。全部線程都有一個線程號,也就是Thread ID。其類型爲pthread_t。經過調用pthread_self()函數能夠得到自身的線程號。一個線程不可以被多個線程等待。
(4)終止線程:兩種方式:----------------這個函數寫在被調用子線程中
1)線程自身調用pthread_exit()函數
原型:void pthread_exit(void *retval), 使用函數pthread_exit退出線程,這是線程的主動行爲; 線程經過調用pthread_exit函數終止執行,就如同進程在結束時調用exit函數同樣。這個函數的做用是,終止調用它的線程。函數參數是函數返回時的代碼。【注意一點的是這函數必須在pthread_join()函數第二個參數不爲NULL的前提下才可以執行 】----------------這個pthread_exit()函數寫在被調用子線程中
2)其餘線程調用pthread_cancel()函數;這種方式相似於其它線程給咱們的被調用線程發送了一個終止信號;這個函數的原型是:
int pthread_cancel(pthread_t thread),這個函數惟一的參數就是咱們要終止的線程的標識符。----------------這個函數寫在主線程中
其實子線程還能夠設置本身的屬性來決定是否被容許取消,若是容許以何種方式取消。pthread_setcancelstate()函數決定着線程是否容許接收取消請求,它的第一個參數能夠設置爲PTHREAD_CANCEL或者是PTHREAD_CANCEL_DISABLE,它的第二個參數設置爲NULL;----------------這個函數寫在被調用子線程中
若是子線程設置爲容許被取消,則還須要設置是當即取消仍是延遲取消,pthread_setcanceltype()函數的第一個參數能夠是 PTHREAD_CANCEL_DEFERRED(延遲取消)或者是 PTHREAD_CANCEL_ASYNCHRONOUS(當即取消),函數的第二個參數爲NULL 。----------------這個函數寫在被調用子線程中 【注意的是pthread_setcanceltype()函數要和pthread_cancel()函數結合起來使用】
(5)線程屬性以及對其屬性的限制:線程屬性的數據結構是pthread_attr_t,它本質是一個聯合體union。操做線程屬性須要特定的函數。
1)pthread_attr_init(pthread_attr_t *__attr)函數,它的做用是對一個線程屬性對象進行初始化,----------------這個函數寫在主線程中
2)在線程屬性對象的初始化完成後咱們能夠調用其餘相關函數來設置線程的各類屬性:【注意2.1和2.2的前提條件都是pthread_attr_init初始化後】
2.1)設置線程的分離狀態屬性:
所謂線程的分離狀態也就是說讓一個線程不用被主線程等待回收,而是本身去回收本身,本身終止本身,並釋放系統資源。
設置線程分離狀態的函數爲pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)函數, 第二個參數可選爲PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)----------------這個函數寫在主線程中
2.2)設置線程的優先級屬性:線程的優先級用來指定線程的執行規則,使得重要的線程有機會被優先執行,它存放在結構體sched_param中;線程的優先級使用pthread_attr_getschedparam()函數和pthread_attr_setschedparam()設置。咱們通常是(1)先讀取優先級,(2)對取得的值進行修改(3)再存放回去。----------------這個函數寫在主線程中
(6)線程同步:目前實現線程同步的主要方式就是:互斥量、條件變量、信號量
線程的最大特色是資源的共享性,但資源共享中的同步問題是多線程編程的難點。linux下提供了多種方式來處理線程同步,最經常使用的是互斥鎖、條件變量和信號量。
一、互斥量:互斥量提供了對共享資源的保護訪問,當一個線程使用互斥量鎖定某個資源後,其餘線程對該資源的訪問都會被阻塞。 用於保護臨界區(共享資源),以保證在任什麼時候刻只有一個線程可以訪問共享的資源。 互斥量類型聲明爲pthread_mutex_t數據類型,在<bits/pthreadtypes.h>中有具體的定義。
互斥量初始化和銷燬:
/* Initialize a mutex. */
int pthread_mutex_init (pthread_mutex_t *__mutex,\
__const pthread_mutexattr_t *__mutexattr);
/* Destroy a mutex. */
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
若是互斥量是靜態分配的,能夠經過常量進行初始化,以下:pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;
固然也能夠經過pthread_mutex_init()進行初始化。對於動態分配的互斥量因爲不能直接賦值進行初始化就只能採用這種方式進行初始化,pthread_mutex_init()的第二個參數是互斥量的屬性,若是採用默認的屬性設置,能夠傳入NULL。
當不在須要使用互斥量時,須要調用pthread_mutex_destroy()銷燬互斥量所佔用的資源。
對互斥量的兩個基本操做:加鎖和解鎖
/* Try locking a mutex. */
int pthread_mutex_trylock (pthread_mutex_t *__mutex);
/* Lock a mutex. */
int pthread_mutex_lock (pthread_mutex_t *__mutex);
/* Unlock a mutex. */
int pthread_mutex_unlock (pthread_mutex_t *__mutex);
這裏要強調的是:互斥量是用於上鎖的,不能用於等待。
簡單說就是,互斥量的使用流程應該是:線程佔用互斥量,而後訪問共享資源,最後釋放互斥量。而不該該是:線程佔用互斥量,而後判斷資源是否可用,若是不可用,釋放互斥量,而後重複上述過程。這種行爲稱爲輪轉或輪詢,是一種浪費CPU時間的行爲。
二、條件變量:
三、信號量:
信號量用來控制對共享資源的訪問,同步線程,初始化和註銷函數分別是:sem_init()和sem_destroy()函數。sem_post()函數用來增長信號量的值,加1,sem_wait()用來阻塞當前線程直到信號量的值大於0,解除阻塞後將信號量的值減1,以代表資源通過使用後減小。
1、什麼是信號量
線程的信號量與進程間通訊中使用的信號量的概念是同樣,它是一種特殊的變量,它能夠被增長或減小,但對其的關鍵訪問被保證是原子操做。若是一個程序中有多個線程試圖改變一個信號量的值,系統將保證全部的操做都將依次進行。而只有0和1兩種取值的信號量叫作二進制信號量,在這裏將重點介紹。而信號量通常經常使用於保護一段代碼,使其每次只被一個執行線程運行。咱們可使用二進制信號量來完成這個工做。
2、信號量的接口和使用
信號量的函數都以sem_開頭,線程中使用的基本信號量函數有4個,它們都聲明在頭文件semaphore.h中。
代碼示例:
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//線程函數
void *thread_func(void *msg);
sem_t sem;//信號量
#define MSG_SIZE 512
int main()
{
int res = -1;
pthread_t thread;
void *thread_result = NULL;
char msg[MSG_SIZE];
//初始化信號量,其初值爲0
res = sem_init(&sem, 0, 0);
if(res == -1)
{
perror("semaphore intitialization failed\n");
exit(EXIT_FAILURE);
}
//建立線程,並把msg做爲線程函數的參數
res = pthread_create(&thread, NULL, thread_func, msg);
if(res != 0)
{
perror("pthread_create failed\n");
exit(EXIT_FAILURE);
}
//輸入信息,以輸入end結束,因爲fgets會把回車(\n)也讀入,因此判斷時就變成了「end\n」
printf("Input some text. Enter 'end'to finish...\n");
while(strcmp("end\n", msg) != 0)
{
fgets(msg, MSG_SIZE, stdin);
//把信號量加1
sem_post(&sem);
}
printf("Waiting for thread to finish...\n");
//等待子線程結束
res = pthread_join(thread, &thread_result);
if(res != 0)
{
perror("pthread_join failed\n");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
//清理信號量
sem_destroy(&sem);
exit(EXIT_SUCCESS);
}
void* thread_func(void *msg)
{
//把信號量減1
sem_wait(&sem);
char *ptr = msg;
while(strcmp("end\n", msg) != 0)
{
int i = 0;
//把小寫字母變成大寫
for(; ptr[i] != '\0'; ++i)
{
if(ptr[i] >= 'a' && ptr[i] <= 'z')
{
ptr[i] -= 'a' - 'A';
}
}
printf("You input %d characters\n", i-1);
printf("To Uppercase: %s\n", ptr);
//把信號量減1
sem_wait(&sem);
}
//退出線程
pthread_exit(NULL);
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------
3.7.3.線程常見函數(一個線程的核心就是pthread_create函數中調用的一個函數)
其實在Linux中,新建的線程並非在原先的進程中,而是系統經過一個系統調用clone()。該系統copy了一個和原先進程徹底同樣的進程,並在這個進程中執行線程函數。不過這個copy過程和fork不同。 copy後的進程和原先的進程共享了全部的變量,運行環境。這樣,原先進程中的變量變更在copy後的進程中便能體現出來。
每個線程都會有本身獨立的棧。
3.7.3.一、線程建立與回收
(1)pthread_create 主線程用來創造子線程的
/*#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
*/
(2)pthread_join
/*int pthread_join(pthread_t thread, void **retval);//注意這裏的retval是一個二重指針 */
主線程用來等待(意味着阻塞,有點相似於之前的wait函數)回收子線程利用的資源
一個線程不能被多個線程等待,也就是說對一個線程只能調用一次pthread_join,不然只有一個能正確返回,其餘的將返回ESRCH 錯誤。
(3)pthread_detach 主線程用來分離子線程,分離後主線程沒必要再去回收子線程,而是讓子線程本身去回收本身,本身去管理本身,主線程就再也不管了
3.7.3.二、線程取消
(1)pthread_cancel
/*int pthread_cancel(pthread_t thread);*/
函數調用成功返回0。通常都是【主線程調用】該函數去取消(讓它趕忙死)子線程(得有相應的權限)
(2)pthread_setcancelstate
/*int pthread_setcancelstate(int state, int *oldstate);*/ state:終結狀態,能夠爲PTHREAD_CANCEL_DISABLE或者PTHREAD_CANCEL_ENABLE。【子線程調用】設置本身是否容許被取消
(3)pthread_setcanceltype
/*int pthread_setcanceltype(int type, int *oldtype);*/type:要設置的狀態,能夠爲PTHREAD_CANCEL_DEFERRED或者爲PTHREAD_CANCEL_ASYNCHRONOUS。
3.7.3.三、(子)線程函數退出相關(指的是子線程本身退出)
(1)pthread_exit與return退出(最正規的作法)
一個線程的結束有兩種途徑,一種是函數結束了,調用它的線程也就結束了;另外一種方式是經過函數pthread_exit來實現。
(2)pthread_cleanup_push(壓棧)
(3)pthread_cleanup_pop(彈棧)
注意在線程裏不能用exit函數來退出,由於在線程裏一旦用這種方式退出則整個程序就退出了。
線程鎖:(上廁所的例子)
int lock=0;
if(lock==0) //判斷是否處於解鎖狀態
{
(1)lock++; //表示置鎖
(2)pthread_cleanup_push //(壓棧)函數,這個函數就是負責未來解鎖,怕死在裏面出不來了(更保險更安全一點)
pthread_cleanup_push(function,arg);
pthread_cleanup_push(function1,arg);
(3)//須要執行的東西
//子線程有可能在這個步驟中被主線程取消cancle(子線程死在廁所裏,別人也進不去了)
//解決方案就是pthread_cleanup_push(壓棧)函數
(4)pthread_cleanup_pop(0);
pthread_cleanup_pop(0); //參數是0表示把前面pthread_cleanup_push函數壓棧的內容彈出並不執行,若是是1表示把前面pthread_cleanup_push函數壓棧的內容彈出並執行。總結就是pthread_cleanup_pop函數和pthread_cleanup_push函數是一種避免死鎖的一種安全機制。
(5)lock--; //表示解鎖
}
//這個function函數就是負責解鎖的
void function(void * arg)
{
lock--; //表示解鎖
}
posix thread(posix標準)
3.7.3.四、獲取線程id函數
(1)pthread_self
3.7.4_5.線程同步之信號量:
線程同步:
3.7.4.一、任務:用戶從終端輸入任意字符而後統計個數顯示,輸入end則結束
代碼示例:
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*用戶從終端輸入任意字符而後統計個數顯示,輸入end則結束*/
int main(void)
{
char buf[100]={0};
char a[10]={"end"};
printf("請輸入一個字符串:「end」結束.\n");
while(1)
{
memset(&buf,0,sizeof(buf));
read(0,&buf,sizeof(buf));
printf("您輸入的字符串是:[%s]\n",buf);
if(!strncmp(buf,a,3))
{
break;
}
else{
printf("您輸入的字符串個數是:%d\n",(strlen(buf)-1));
}
}
printf("您輸入的字符串是:‘end’,程序退出\n");
return 0;
}
3.7.4.二、使用多線程實現:主線程獲取用戶輸入並判斷是否退出,子線程計數
(1)爲何須要多線程實現
(2)問題和困難點是?不能讓子線程循環執行。
(3)理解什麼是線程同步:【子線程阻塞住本身,主線程去激活子線程。讓兩個線程去配合工做】
3.7.4.三、信號量的介紹和使用
線程的信號量與進程間通訊中使用的信號量的概念是同樣,它是一種特殊的變量,它能夠被增長或減小,但對其的關鍵訪問被保證是原子操做。若是一個程序中有多個線程試圖改變一個信號量的值,系統將保證全部的操做都將依次進行。而只有0和1兩種取值的信號量叫作二進制信號量,在這裏將重點介紹。而信號量通常經常使用於保護一段代碼,使其每次只被一個執行線程運行。咱們可使用二進制信號量來完成這個工做.
3.7.6.線程同步之互斥鎖
3.7.6.一、什麼是互斥鎖
(1)互斥鎖又叫互斥量(mutex)
(2)相關函數:
pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_unlock
(3)互斥鎖和信號量的關係:能夠認爲互斥鎖是一種特殊的信號量(互斥鎖是值只能是0和1的信號量)
(4)互斥鎖主要用來實現關鍵段代碼保護
3.7.6.二、用互斥鎖來實現上節的代碼
(1)上鎖 pthread_mutex_lock()
(2)訪問
(3)解鎖pthread_mutex_unlock()
注意:man 3 pthread_mutex_init時提示找不到函數,說明你沒有安裝pthread相關的man手冊。安裝方法:一、虛擬機上網;二、sudoapt-get install manpages-posix-dev
3.7.7.線程同步之條件變量
3.7.7.一、什麼是條件變量
3.7.7.二、相關函數
pthread_cond_init
pthread_cond_destroy
pthread_cond_wait
pthread_cond_signal(只能喚醒一個)/pthread_cond_broadcast(廣播激活多個線程)
3.7.7.三、使用條件變量來實現上節代碼
3.7.7.四、線程同步總結