Linux 線程基礎

什麼是線程 什麼是進程

線程是計算機中運算調度的最小單位,
他在進程的地址空間內運行。 是進程實際運作單位
一條線程是進程中單一順序的執行流
。線程使操作系統調度執行的基本單位。

而進程是操作系統中資源分配的基本單位,進程有自己獨立的地址空間(頁表),文件描述符管理的文件,IO設備,操作系統通過這些一定程度防止進程間資源的衝突。

???爲什麼vfork 保證子進程先執行 因爲公用同一地址空間 父子進程同時在一個調用棧裏跑 調用棧會混亂

內核角度看線程

在操作系統內核眼中 不區分進程和線程 他的眼中只有task_struct 要執行一個線程 (輕量級進程)/進程(單進程進程 或 主線程) 將其task_struct放入執行隊列中

把一個線程掛起 就是把他放在掛起隊列中

每一個進程有多個線程 也就是有多個PCB (task_struct) 內核通過task_struct來控制調度線程/進程

getpid() 返回的是task_struct結構體中的 pid_t tgid; 一個進程中的多個線程叫一個線程組 組id tgid
gettid() 返回值是task_struct結構體中的 pid_t pid

一個進程執行後 默認打開了第一個線程叫主線程 主線程pid tgid 一樣 單線程進程中 只有主線程

線程組中除了主線程之外 其他線程是對等的 沒有類似進程的父子關係.
線程組id等於進程中第一個線程id(主線程id)。tgid。

創建一個線程內核中多了一個縣城的PCB,但是這個PCB和其進程組內的其他線程共用一份地址空間(頁表)。
這裏寫圖片描述

線程間的公有和私有

線程之間的 共享:
共享虛擬地址空間 數據段 代碼段 堆 共享區 命令行參數和環境變量 (本質上是共享頁表) 虛擬地址空間映射到同一物理地址
代碼段也是共享的 只不過不同的線程跑的那個入口函數不一樣 主線程也可以跑 其他線程的代碼
文件描述符表 每種信號的處理方式 或自定義的信號處理函數 (本質上是 每一個線程的PCB中指向handler表的指針相同)

線程之間的私有
進程間應爲各自獨立的虛擬地址空間 和頁表 使得進程間通信麻煩
調用棧 每個線程有自己的調用找 (主要是爲了線程之間各自跑各自的不要互相干擾)
***這個棧 不是權限的限制 他的意思是 兩個線程在同一虛擬地址空間的棧上各自擁一份調用棧 但是每個線程可以訪問修改其他線程棧上的空間 只要這個空間是合法的。

線程的上下文 (本質上是當前線程執行時推出的時 寄存器的PC指針 存到線程的PCB)
線程ID
儲存錯誤碼的變量 errno
信號屏蔽字 (各自線程PCB中指向 各自不同的block表)
調調度度優先級
線程是調度的最小單元

線程的經典運行場景

爲什麼linux搞一個輕量級進程 因爲爲了實現任務調度的時候比較簡單 內核代碼的複用。

計算密集型應用 把計算問題分成幾個部分 讓多個核一起跑

I/O密集型應用 按下按鈕的一瞬間 立刻創建一個多線程 讓計算和I/O處理在時間上重疊。

進程線程的優劣

多線程對於多進程的好處:
創建 銷燬 線程比創建進程的成本低得多
線程間切換比進程間切換成本低

爲什麼 : 線程的共用 。。。。。 不需要創建進程時大量的內存拷貝 內存釋放 等等

壞處:
多線程的代碼
調用調試難度提高 保證各個線程按照預期的順序執行 同步.
對各個線程 的公共資源操作的互斥
一個線程崩潰 會導致整個進程都終止 一個線程調用一些系統調用會影響整個進程 signal() exit()

進程線程不是越多越好
看cpu資源 有幾個核。處理速度。

使用命令查看線程

1、 ps -eLf 查看當前操作系統中的所有線程。
2、 pstack 查看某個進程包含的線程以及線程的調用棧。
3、 gdb into thread查看進程中的所有線程, thread +【線程id】進入該線程,bt查看線程的調用棧。

ps -eLf | head -1 && ps -eLf | grep a.out

POSIX線程庫

因爲Linux內核並不區分進程和線程,所以Linux沒有關於線程的系統調用。
POSIX 線程庫: 將操作進程的系統調用封裝成線程庫。

想得到內核中的線程id 可以通過 pid_t tid = syscall(SYS_getttid);
主線程的tid等於進程id
線程中的group_leader指針指向主線程。

1、創建線程。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

pthread_t    pthread_self();

用該函數創建線程之前需要先定義pthread_t 類型的線程id。
這個線程id和線程PCB中的tid不是一個, tid是爲了內核調度使用,pthread_t 是用戶在POSIX線程庫中使用的

pthread_create()函數第一個參數值pthread_t*類型的指針,第二個參數設置線程的屬性,默認填NULL,第三個參數填線程入口函數的地址,第四個參數是允許的向線程入口函數中傳的一個參數。成功返回0,失敗返回錯誤碼。

2、 線程終止

  1. 線程入口函數調用return。(不適用於主線程)
    2、線程調用pthread_exit ()函數終止自己
    3、線程調用pthread_cancel()函數終止同一進程的其他線程
 #include <pthread.h>
 void pthread_exit(void *retval); 
 int  pthread_cancel(pthread_t id);      

retval指針指向一個全局變量。 或堆空間。返回線程的處理結果。
id是要讓哪個線程退出。

3、線程等待與分離

因爲線程退出的時侯,線程PCB保留了線程的退出狀態,有時需要被讀取,有其他線程接收退出了的線程的PCB,他纔會被釋放否則內存泄漏。

int pthread_join(pthread_t thread, void **retval);

該函數負責掛起等待thread線程,  retval指向被等待線程的推出狀態或返回值。
不關心可以設置爲NULL。

int  pthread_detach(pthread_t   thread)l

如果一個線程並不關心某線程的返回值就用該函數進行線程分離,被分離的線程可以自己退出時自己清理自己的線程資源。

線程共享地址空間和棧上的變量。
代碼:
https://github.com/xym97/Linux/tree/master/thread/thread2.c
https://github.com/xym97/Linux/tree/master/thread/thread3.c

關於多線程提高效率

CPU的核心數和處理速度有限。
多線程可以充分利用CPU資源,提高CPU利用率。
但是線程不是越多越好, 多線程在一起會競爭同一個鎖效率不一定高,
關鍵還是看CPU資源夠不夠。
如果是I/O密集型任務, 速度瓶頸主要在I/O上不在CPU上。

代碼:
https://github.com/xym97/Linux/blob/master/thread/thread4.c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章