1、線程概念
1.1、線程和進程的對比
- 線程和進程類似,二者都有PCB
- 二者的底層函數都是一樣的,都是使用到clone
- 進程可以變成線程
- 在Linux下,線程最是小的執行單位;進程是最小的分配資源單位
- .從內核裏看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三 級頁表是相同的。
在下圖中,A是一個進程,只有一個PCB,b他創建一個線程就多一個PCB,但是他們共享在一個資源。
1.2、線程之間 共享的資源
- 文件描述符表
- 每種信號的處理方式
- 當前工作目錄
- 用戶ID 和 組ID
- 內存地址空間
1.3、線程之間 不共享的資源
- 線程id
- 處理器現場和棧指針(內核棧)
- 獨立的棧空間(用戶空間棧)
- errno變量
- 信號屏蔽字
- 調度優先級
1.4、線程優缺點解析
1、優點:
- 提高程序的併發性
- 開銷小,不用重新分配內存
- 通信和共享數據方便
2、缺點
- 線程不穩定(庫函數實現)
- 線程調試比較困難(gdb支持不好)
- 線程無法使用unix經典事件,例如信號
1.5、線程的注意點
1、主線程一結束,子線程也會跟着結束(不管你子線程有沒有執行完)。
2、線程之間,變量是共享的。
3、在創建函數中,錯誤不能用perror打印。
2、pthread解析
2.1察看LWP號
在終端輸入ps -elf,就可以查看線程的LWP號
2.2 創建線程函數pthread_create
- 頭文件:#include <pthread.h>
- 函數:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- 參數解析
- 參數1: pthread_t *thread,這裏需要傳遞一個類型爲pthread_t的變量,來保存創建出來新線程的ID
- 參數2:const pthread_attr_t *attr:線程屬性設置,如使用默認屬性,則傳NULL。這裏我們一般在寫代碼的時候都是使用NULL,設置成默認的。
- 參數3:void *(*start_routine) (void *):函數指針,指向新線程應該加載執行的函數模塊。就是寫這個線程被創建出來後要執行的函數。
- 參數4:void *arg:指定線程將要執行調用的那個函數的參數 (即這個參數是要傳到,要執行函數裏的)
- 返回值:成功返回0,失敗返回錯誤號。(判斷函數執行是否有錯誤不能用perror打印)
示例代碼
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
//線程要執行的函數
void *run(void* arg)
{
int i = (int)arg;
printf("我是第%d個子線程\n",i+1);
}
int main(void)
{
//這裏我創建pthread_t數組,來創建多個子線程
pthread_t tid[5];
//這個用於保存創建線程時產生的錯誤號
int err;
//創建線程
for(int i = 0;i<5;i++)
{
//創建線程,第二個參數傳NULL,第三個傳執行函數,參數4爲傳入執行函數的參數
//這裏我傳進去的i 要強轉成 void* 纔行
err = pthread_create(&tid[i],NULL,run,(void*)i);
//錯誤處理
if (err != 0)
{
//錯誤信息處理,方便顯示到終端
fprintf(stderr, "can't create thread: %s\n", strerror(err));
exit(1);
}
}
sleep(i);
return 0;
}
注意點:
1、當子線程執行完指定的函數(就是第三個參數start_routine)之後,這個線程就退出了(這個線程就死了),其他線程可以使用pthread_join來獲取start_routine函數的返回值,並且回收結束的線程(收屍操作)。
2、當創建線程函數pthread_create結束後新的ID會被寫到第一個參數(pthread_t *thread)裏,可以用pthread_self()函數獲取。
2.2 pthread_self獲取調用線程tid
- 頭文件:#include <pthread.h>
- 函數:pthread_t pthread_self(void);
printf("In mian thread id = %u \n",pthread_self());
通過pthread_self函數就能獲取id。每一個線程的id號都是不一樣的。
2.3 pthread_exit線程退出函數
- 頭文件:#include <pthread.h>
- 函數:void pthread_exit(void *retval);
- 參數: void *retval:線程退出時傳遞出的參數,可以是退出值或地址,如是地址時,不能是線程內部申請的局部地址。
- 注意點:
- 調用線程退出函數,注意和exit函數的區別,任何線程裏exit導致進程退出(進程一旦退出就意味着說有的進程都終止退出)。其他線程 未工作結束,主控線程退出時不能return或exit。
- pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是 用malloc分配的,否則會內存出錯!
2.4 pthread_join回收線程
- 頭文件#include <pthread.h>
- 函數int pthread_join(pthread_t thread, void **retval);
- 參數解析
- 參數1:pthread_t thread:回收線程的tid
- 參數2:void **retval:接收退出線程傳遞出的返回值,一般這個參數我們都填空,只讓這個函數起到回收進程的作用,如果需要用到該返回值,則看下面的注意點
- 返回值:成功返回0,失敗返回錯誤號
- 注意:第二個參數的情況有很多種
- 如果thread線程通過return返回,retval所指向的單元裏存放的是thread線程函數的返 回值。
- 如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元裏存 放的是常數PTHREAD_CANCELED。
- 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給 pthread_exit的參數。
- 如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。
2.4 pthread_cancel結束其他線程
- 頭文件:#include <pthread.h>
- 函數:int pthread_cancel(pthread_t thread);
- 參數1:pthread_t thread:要結束的線程的tid
2.5 pthread_detach分離線程
- 頭文件:#include <pthread.h>
- 函數:int pthread_detach(pthread_t tid);
- 參數1:pthread_t thread::分離線程tid
- 返回值:成功返回0,失敗返回錯誤號。
解析:
- 該函數的作用和pthread_join類似,他的作用是線程也可以被置爲detach狀態,這樣的線程一旦終止就立刻回收 它佔用的所有資源,而不保留終止狀態。
- 不能對一個已經處於detach狀態的線程調用 pthread_join,這樣的調用將返回EINVAL。(即,如果已經對一個線程調用了pthread_detach就不 能再調用pthread_join了)。
3、示例代碼
3.1、c代碼示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
int var = 100; //定義一個全局變量來驗證變量共享
void *run(void* arg)
{
int i = (int)arg;
sleep(i);
if(i == 1) //第一個線程對變量修改
{
var =111;
printf("var = %d\n",var);
return var;
}
else if(i == 3) //第四個線程對變量進行修改
{
var =333;
printf("var = %d\n",var);
printf("我是第%d個線程,我結束了自己\n",i+1);
//結束自己 pthread_exit ,只要其他線程還在,你自殺後不能return或exit
pthread_exit((void *)var);
//pthread_detach(pthread_t tid)可以殺死其他線程
}
else
{
pthread_exit((void *)var);
}
return NULL;
}
int main(void)
{
pthread_t tid[5];
int *ret[5];
int i;
//看看主線程的線程id和進程ID
printf("In mian thread id = %u , pid = %u\n",pthread_self(),getpid());
//創建線程
for(i = 0;i<5;i++)
{
//創建線程,第二個參數傳NULL,第三個傳執行函數,參數4爲傳入執行函數的參數
pthread_create(&tid[i],NULL,run,(void*)i);
}
//回收多個子線程 pthread_join,不回收會造成殭屍線程
for(i = 0;i<5;i++)
{
//二級指針提供內存的讀取和修改的,線程的返回值存在*ret中
pthread_join(tid[i],(void**)&ret[i]);
}
printf("in main id = %u, var = %d\n",pthread_self(),var);
sleep(i);
return 0;
}
3.2、C++封裝線程
頭文件
#ifndef BASETHRED_H
#define BASETHRED_H
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
class CBaseThread
{
public:
CBaseThread();
~CBaseThread();
//創建,並且啓動線程
void start();
//自定義處理函數,這裏定義成虛函數,每個子類的自己的run都不一樣
virtual int run()=0;
private:
//線程類的處理函數,注意這裏要設置成靜態
static void* rountine(void *arg);
protected:
bool m_bRun; //運行標誌位
bool m_bJoin; //是否回收
pthread_t m_tid; //線程ID
}
#endif
cpp文件
#include "CBase_pthread.h"
CBaseThread::CBaseThread()
:m_bRun(false),m_bJoin(false)
{
}
CBaseThread::~CBaseThread()
{
}
void CBaseThread::start()
{
//判斷線程是否已經創建
if(m_bRun == false)
{
//我這裏是把this(CBaseThread這個類的)傳進去,要強轉
if(pthread_create(&m_tid,NULL,rountine,(void*)this)!=0)
{
perror("create thread error:\n");
}
}
}
//處理子類的自定義函數
void* CBaseThread::rountine(void *arg)
{
//先把傳進來的this給他強轉回來,用這個thr接收
CBaseThread *thr = (CBaseThread*)arg;
if(thr->m_bJoin)
{
//自分離,不用調用join函數等待
//的線程一旦終止就立刻回收 它佔用的所有資源,而不保留終止狀態。
pthread_detach(pthread_self());
}
thr->m_bRun = true;
thr->run(); //用戶自定義處理函數
thr->m_bJoin = false;
pthread_exit(NULL);
}
}
主函數
#include "CBase_pthread.h"
#include <iostream>
using namespace std;
int main(void)
{
CBaseThread my_Thread;
my_Thread.start();
sleep(1);
return 0;
}