首先大家可以看看這篇文章,這篇文章對進程和線程的關係做了生動的講解:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
什麼是進程
前面我們學習了進程的相關知識,現在我們先來做以下回顧。
進程:進程是指在系統中能夠獨立運行並作爲資源分配的基本單位,它是由一組機器指令、數據和堆棧等組成,並且每一個進程都有一個進程控制塊PCB,在Linux下被稱爲task_struct;另外每個進程還有一個獨立的地址,它和物理內存通過頁表聯繫在一起。
什麼是線程
線程:線程是進程的一個部分,它在進程的地址空間上運行,和主線程一起共享虛擬地址空間的資源,一個沒有線程的進程可以看做是單線程,線程主要是作爲系統調度和分派的基本單位,Linux下沒有線程的概念,系統將線程看做輕量級進程,這裏的輕量級是因爲線程沒有自己獨立的地址空間和相關數據結構。
進程和線程的區別
1.首先比較重要的一點是,進程是擁有系統資源的一個獨立單位,而線程不能擁有資源,但它可以訪問創建它的進程中的資源。
2.任一時刻,cpu只能運行一個進程,其他進程則處於非運行狀態,而任一時刻,線程可以同時執行,還可以協同完成一項任務,這是線程的併發性。
3.當我們創建一個進程時,系統通過寫時拷貝的方式給被創建的進程分配了地址空間和相關數據結構,而當我們創建一個線程時,這個線程會和當前進程中的其他線程一起共享地址空間中的資源。
線程之間的共享和私有資源
首先,各個進程之間共享同一個虛擬地址空間,所以,它的文本段和數據段都是共享的,另外,各個線程還共享文件描述符表,還有用戶id和用戶組,但是有些資源各個線程之間也不是相互共享的,例如:線程id、硬件上下文(PC指針,各個寄存器變量的值,還有棧指針等等)、棧空間、調度優先級。
下面我們寫一個例子來驗證一下:
線程標識
進程ID在整個系統中是唯一的,但是線程ID不同,線程ID只有在它所屬的進程上下文中才有意義。
線程ID是用pthread_t數據類型來表示的;
pthread_t pthread_self(void) //用來獲得當前線程的ID的函數
線程創建
新的線程可以通過調用pthread_create函數創建,
int pthread_create (pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void* (*start_rtn)(void*),void* restrict arg);
當pthread_create成功返回時,新創建的線程的線程ID會被設置成tidp指向的內存單元。
attr參數用於定製各種不同的線程屬性,當使用默認屬性時該參數設置爲NULL即可。
新創建的線程從start_rtn指向的函數開始運行。該函數只有一個無參類型指針arg,可以傳入任何類型的參數,如果要傳入多個參數需要放到結構體中;
pthread_create函數在調用失敗時會返回錯誤碼;注意在pthread.h的頭文件中,大部分函數返回值都被設置爲成功返回0,失敗則返回錯誤碼errno這種形式。
線程終止
單個線程可以以下面三種方式退出:
1. 線程可以簡單的從啓動例程中返回,因此可以在不終止整個進程的情況下,停止它的控制流。
2. 線程可以被同一進程中的其他線程取消。
3. 線程調用pthread_exit函數。
void pthread_exit(void* rval_ptr);
rval_ptr:參數是一個無類型的指針,相當於線程的退出碼,與傳給啓動例程的單個參數類似。進程中的其他線程可以調用pthread_join來訪問到這個指針。
int pthread_join(pthread_t thread,void** rval_ptr/**));
//返回值:若成功,返回0,若失敗,返回錯誤編號
調用了該函數的線程將一直阻塞,直到指定的線程調用pthread_exit或從啓動線程中返回。如果線程簡單地從它的啓動例程中返回,rval_ptr就包含返回碼;
如果線程被取消(pthread_excl),由rval_ptr指定的內存單元會被設置爲PTHREAD_CANCELED,如果對線程返回值不感興趣,則可以將rval_ptr設置爲NULL;(PTHREAD_CANCELED在頭文件中被設置爲-1)
取消線程
int pthread_cancel(pthread_t tid)
//成功返回0,失敗則返回錯誤編號
線程可以調用該函數來請求取消同一進程中的其他線程,被取消的線程的退出碼會被設置爲PTHREAD_CANCELED.
## 線程清理程序
void pthread_cleanup_push(void (*rtn)(void*),void *arg);
**rtn**:是一個指向線程清理函數的函數指針,可以向清理函數傳遞參數.
pthread_cleanup_push在什麼情況下被調用:
1.當調用pthread_exit時,(注意pthread_exit要放在pthread_cleanup_pop之前)
2.響應取消請求時,
3.用非零execute參數調用pthread_cleanup_pop時纔會響應清理函數請求。
注意:
1.線程函數如果在pthread_clean_push和pthread_clean_pop之間返回時會產生未定義的後果,可以在兩函數之間pthread_exit.
2.pthread_cleanup_push和pthread_cleanup_pop兩個函數要相互匹配使用。
下面我們對線程清理程序做個測試:
1.在線程函數中返回:
2.調用pthread_exit函數:
3.調用pthread_cleanup_pop(0):
另外,還可以通過在其他線程中調用pthread_cancel函數,或者在線程執行函數中調用execute不爲0的pthread_cleanup_pop函數來達到調用線程清理函數的效果。
下面附上測試代碼:
/*************************************************************************
> File Name: Pthread.c
> Author: LZH
> Mail: [email protected]
> Created Time: 2017年02月17日 星期五 05時35分49秒
************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void CleanPthread(void* arg)
{
printf("I am in CleanPthread...\n");
printf("tid::%d,cleanning...\n",pthread_self());
}
void* Func(void* arg)
{
int count=3;
pthread_cleanup_push(CleanPthread,NULL); //clean new pthread
while(count--){
printf("I am new thread...,tid%d,pid:%d\n",pthread_self(),getpid());
sleep(1);
}
sleep(5);
pthread_cleanup_pop(0);
printf("The End..\n");
//return (void*) 0;
}
int main()
{
pthread_t tid=0;
int* status=0;
int ret=pthread_create(&tid, NULL,Func,(void*)&status);
if(ret < 0){
perror("pthread_create..\n");
return -1;
}
int count=3;
while(count--)
{
printf("I am main pthread,tid:%d,pid:%d\n",pthread_self(),getpid());
sleep(1);
}
//pthread_cancel(tid);
sleep(3);
void *r_val;
ret=pthread_join(tid,&r_val);
printf("PTHREAD_CANCELED:%d\n",PTHREAD_CANCELED);
printf("ret code:%d,ret=%d\n",(long)r_val,ret);
return 0;
}