Linux基礎——線程

1、線程概念

1.1、線程和進程的對比

  1. 線程和進程類似,二者都有PCB
  2. 二者的底層函數都是一樣的,都是使用到clone
  3. 進程可以變成線程
  4. 在Linux下,線程最是小的執行單位;進程是最小的分配資源單位
  5. .從內核裏看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三 級頁表是相同的。

在下圖中,A是一個進程,只有一個PCB,b他創建一個線程就多一個PCB,但是他們共享在一個資源。
在這裏插入圖片描述

1.2、線程之間 共享的資源

  1. 文件描述符表
  2. 每種信號的處理方式
  3. 當前工作目錄
  4. 用戶ID 和 組ID
  5. 內存地址空間

1.3、線程之間 不共享的資源

  1. 線程id
  2. 處理器現場和棧指針(內核棧)
  3. 獨立的棧空間(用戶空間棧)
  4. errno變量
  5. 信號屏蔽字
  6. 調度優先級

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,失敗返回錯誤號。

解析:

  1. 該函數的作用和pthread_join類似,他的作用是線程也可以被置爲detach狀態,這樣的線程一旦終止就立刻回收 它佔用的所有資源,而不保留終止狀態。
  2. 不能對一個已經處於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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章