[TOC]
# 線程概念
在許多經典的操作系統教科書中,總是把進程定義為程序的執行實例,它并不執行什么, 只是維護應用程序所需的各種資源,而線程則是真正的執行實體。
所以,線程是輕量級的進程(LWP:light weight process),在Linux環境下線程的本質仍是進程。
為了讓進程完成一定的工作,進程必須至少包含一個線程。

進程,直觀點說,保存在硬盤上的程序運行以后,會在內存空間里形成一個獨立的內存體,這個內存體有自己的地址空間,有自己的堆,上級掛靠單位是操作系統。操作系統會以進程為單位,分配系統資源,所以我們也說,**進程是CPU分配資源的最小單位**。
線程存在與進程當中(進程可以認為是線程的容器),是操作系統調度執行的最小單位。說通俗點,線程就是干活的。
進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。
線程是進程的一個實體,是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
如果說進程是一個資源管家,負責從主人那里要資源的話,那么線程就是干活的苦力。一個管家必須完成一項工作,就需要最少一個苦力,也就是說,一個進程最少包含一個線程,也可以包含多個線程。苦力要干活,就需要依托于管家,所以說一個線程,必須屬于某一個進程。
進程有自己的地址空間,線程使用進程的地址空間,也就是說,進程里的資源,線程都是有權訪問的,比如說堆啊,棧啊,靜態存儲區什么的。
> 進程是操作系統分配資源的最小單位
>
> 線程是操作系統調度的最小單位
# 線程函數列表安裝
命令:
> sudo apt-get install manpages-posix-dev
【說明】manpages-posix-dev 包含 POSIX 的 header files 和 library calls 的用法
查看:
> man -k pthread
# NPTL
當 Linux 最初開發時,在內核中并不能真正支持線程。但是它的確可以通過 clone() 系統調用將進程作為可調度的實體。這個調用創建了調用進程(calling process)的一個拷貝,這個拷貝與調用進程共享相同的地址空間。LinuxThreads 項目使用這個調用來完全在用戶空間模擬對線程的支持。不幸的是,這種方法有一些缺點,尤其是在信號處理、調度和進程間同步原語方面都存在問題。另外,這個線程模型也不符合 POSIX 的要求。
要改進 LinuxThreads,非常明顯我們需要內核的支持,并且需要重寫線程庫。有兩個相互競爭的項目開始來滿足這些要求。一個包括 IBM 的開發人員的團隊開展了 NGPT(Next-Generation POSIX Threads)項目。同時,Red Hat 的一些開發人員開展了 NPTL 項目。NGPT 在 2003 年中期被放棄了,把這個領域完全留給了 NPTL。
NPTL,或稱為 Native POSIX Thread Library,是 Linux 線程的一個新實現,它克服了 LinuxThreads 的缺點,同時也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩定性方面都提供了重大的改進。
查看當前pthread庫版本:getconf GNU\_LIBPTHREAD\_VERSION
~~~
root@master:~ # getconf GNU_LIBPTHREAD_VERSION
NPTL 2.17
~~~
# 線程的特點
類Unix系統中,早期是沒有“線程”概念的,80年代才引入,借助進程機制實現出了線程的概念。
因此在這類系統中,進程和線程關系密切:
1) 線程是輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone
2) 從內核里看進程和線程是一樣的,都有各自不同的PCB.
3) 進程可以蛻變成線程
4) 在linux下,線程最是小的執行單位;進程是最小的分配資源單位

查看指定進程的LWP號:
> ps ?-Lf ?pid
實際上,無論是創建進程的fork,還是創建線程的pthread\_create,底層實現都是調用同一個內核函數 clone 。
? 如果復制對方的地址空間,那么就產出一個“進程”;
? 如果共享對方的地址空間,就產生一個“線程”。
Linux內核是不區分進程和線程的, 只在用戶層面上進行區分。所以,線程所有操作函數 pthread\_\* 是庫函數,而非系統調用。
# 線程共享資源
1) 文件描述符表
2) 每種信號的處理方式
3) 當前工作目錄
4) 用戶ID和組ID
內存地址空間 (.text/.data/.bss/heap/共享庫)
# 線程非共享資源
1) 線程id
2) 處理器現場和棧指針(內核棧)
3) 獨立的棧空間(用戶空間棧)
4) errno變量
5) 信號屏蔽字
6) 調度優先級
# 線程的優缺點
**優點:**
? 提高程序并發性
? 開銷小
? 數據通信、共享數據方便
**缺點:**
? 庫函數,不穩定
? 調試、編寫困難、gdb不支持
? 對信號支持不好
優點相對突出,缺點均不是硬傷。Linux下由于實現方法導致進程、線程差別不是很大。
# 線程常用操作
## 線程號
就像每個進程都有一個進程號一樣,每個線程也有一個線程號。進程號在整個系統中是唯一的,但線程號不同,線程號只在它所屬的進程環境中有效。
進程號用 pid\_t 數據類型表示,是一個非負整數。線程號則用 pthread\_t 數據類型來表示,Linux 使用無符號長整數表示。
有的系統在實現pthread\_t 的時候,用一個結構體來表示,所以在可移植的操作系統實現不能把它做為整數處理。
pthread\_self函數:
~~~
#include <pthread.h>
?
pthread_t pthread_self(void);
功能:
獲取線程號。
參數:
無
返回值:
調用線程的線程 ID 。
~~~
pthread\_equal函數:
~~~
int pthread_equal(pthread_t t1, pthread_t t2);
功能:
判斷線程號 t1 和 t2 是否相等。為了方便移植,盡量使用函數來比較線程 ID。
參數:
t1,t2:待判斷的線程號。
返回值:
相等: 非 0
不相等:0
~~~
~~~
int main()
{
pthread_t thread_id;
?
thread_id = pthread_self(); // 返回調用線程的線程ID
printf("Thread ID = %lu \n", thread_id);
?
if (0 != pthread_equal(thread_id, pthread_self()))
{
printf("Equal!\n");
}
else
{
printf("Not equal!\n");
}
?
return 0;
}
~~~
【注意】線程函數的程序在 pthread 庫中,故鏈接時要加上參數 -lpthread。
## 線程的創建
pthread\_create函數:
~~~
#include <pthread.h>
?
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
創建一個線程。
參數:
thread:線程標識符地址。
attr:線程屬性結構體地址,通常設置為 NULL。
start_routine:線程函數的入口地址。
arg:傳給線程函數的參數。
返回值:
成功:0
失敗:非 0
~~~
在一個線程中調用pthread\_create()創建新的線程后,當前線程從pthread\_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread\_create的函數指針start\_routine決定。
由于pthread\_create的錯誤碼不保存在errno中,因此不能直接用perror()打印錯誤信息,可以先用strerror()把錯誤碼轉換成錯誤信息再打印。
~~~
// 回調函數
void *thread_fun(void * arg)
{
sleep(1);
int num = *((int *)arg);
printf("int the new thread: num = %d\n", num);
?
return NULL;
}
?
int main()
{
pthread_t tid;
int test = 100;
?
// 返回錯誤號
int ret = pthread_create(&tid, NULL, thread_fun, (void *)&test);
if (ret != 0)
{
printf("error number: %d\n", ret);
// 根據錯誤號打印錯誤信息
printf("error information: %s\n", strerror(ret));
}
?
while (1);
?
return 0;
}
~~~
## 線程資源回收
pthread\_join函數:
~~~
#include <pthread.h>
?
int pthread_join(pthread_t thread, void **retval);
功能:
等待線程結束(此函數會阻塞),并回收線程資源,類似進程的 wait() 函數。如果線程已經結束,那么該函數會立即返回。
參數:
thread:被等待的線程號。
retval:用來存儲線程退出狀態的指針的地址。
返回值:
成功:0
失敗:非 0
~~~
~~~
void *thead(void *arg)
{
static int num = 123; //靜態變量
?
printf("after 2 seceonds, thread will return\n");
sleep(2);
?
return #
}
?
int main()
{
pthread_t tid;
int ret = 0;
void *value = NULL;
?
// 創建線程
pthread_create(&tid, NULL, thead, NULL);
?
?
// 等待線程號為 tid 的線程,如果此線程結束就回收其資源
// &value保存線程退出的返回值
pthread_join(tid, &value);
?
printf("value = %d\n", *((int *)value));
?
return 0;
}
~~~
調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread\_join得到的終止狀態是不同的,總結如下:
1) 如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值。
2) 如果thread線程被別的線程調用pthread\_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD\_CANCELED。
3) 如果thread線程是自己調用pthread\_exit終止的,retval所指向的單元存放的是傳給pthread\_exit的參數。
## 線程分離
一般情況下,線程終止后,其終止狀態一直保留到其它線程調用pthread\_join獲取它的狀態為止。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。
不能對一個已經處于detach狀態的線程調用pthread\_join,這樣的調用將返回EINVAL錯誤。也就是說,如果已經對一個線程調用了pthread\_detach就不能再調用pthread\_join了。
pthread\_detach函數:
~~~
#include <pthread.h>
?
int pthread_detach(pthread_t thread);
功能:
使調用線程與當前進程分離,分離后不代表此線程不依賴與當前進程,線程分離的目的是將線程資源的回收工作交由系統自動來完成,也就是說當被分離的線程結束之后,系統會自動回收它的資源。所以,此函數不會阻塞。
參數:
thread:線程號。
返回值:
成功:0
失敗:非0
~~~
# 線程退出
在進程中我們可以調用exit函數或\_exit函數來結束進程,在一個線程中我們可以通過以下三種在不終止整個進程的情況下停止它的控制流。
* 線程從執行函數中返回。
* 線程調用pthread\_exit退出線程。
* 線程可以被同一進程中的其它線程取消。
pthread\_exit函數
~~~
#include <pthread.h>
?
void pthread_exit(void *retval);
功能:
退出調用線程。一個進程中的多個線程是共享該進程的數據段,因此,通常線程退出后所占用的資源并不會釋放。
參數:
retval:存儲線程退出狀態的指針。
返回值:無
~~~
~~~
void *thread(void *arg)
{
static int num = 123; //靜態變量
int i = 0;
while (1)
{
printf("I am runing\n");
sleep(1);
i++;
if (i == 3)
{
pthread_exit((void *)&num);
// return #
}
}
?
return NULL;
}
?
int main(int argc, char *argv[])
{
int ret = 0;
pthread_t tid;
void *value = NULL;
?
pthread_create(&tid, NULL, thread, NULL);
?
?
pthread_join(tid, &value);
printf("value = %d\n", *(int *)value);
?
return 0;
}
~~~
# 線程取消
~~~
#include <pthread.h>
?
int pthread_cancel(pthread_t thread);
功能:
殺死(取消)線程
參數:
thread : 目標線程ID。
返回值:
成功:0
失敗:出錯編號
~~~
注意:線程的取消并不是實時的,而又一定的延時。需要等待線程到達某個取消點(檢查點)。
類似于玩游戲存檔,必須到達指定的場所(存檔點,如:客棧、倉庫、城里等)才能存儲進度。
殺死線程也不是立刻就能完成,必須要到達取消點。
取消點:是線程檢查是否被取消,并按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write..... 執行命令**man 7 pthreads**可以查看具備這些取消點的系統調用列表。
可粗略認為一個系統調用(進入內核)即為一個取消點。
~~~
void *thread_cancel(void *arg)
{
while (1)
{
pthread_testcancel(); //設置取消點
}
return NULL;
}
?
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_cancel, NULL); //創建線程
?
sleep(3); //3秒后
pthread_cancel(tid); //取消tid線程
?
pthread_join(tid, NULL);
?
return 0;
}
~~~
- c語言
- 基礎知識
- 變量和常量
- 宏定義和預處理
- 隨機數
- register變量
- errno全局變量
- 靜態變量
- 類型
- 數組
- 類型轉換
- vs中c4996錯誤
- 數據類型和長度
- 二進制數,八進制數和十六進制數
- 位域
- typedef定義類型
- 函數和編譯
- 函數調用慣例
- 函數進棧和出棧
- 函數
- 編譯
- sizeof
- main函數接收參數
- 宏函數
- 目標文件和可執行文件有什么
- 強符號和弱符號
- 什么是鏈接
- 符號
- 強引用和弱引用
- 字符串處理函數
- sscanf
- 查找子字符串
- 字符串指針
- qt
- MFC
- 指針
- 簡介
- 指針詳解
- 案例
- 指針數組
- 偏移量
- 間接賦值
- 易錯點
- 二級指針
- 結構體指針
- 字節對齊
- 函數指針
- 指針例子
- main接收用戶輸入
- 內存布局
- 內存分區
- 空間開辟和釋放
- 堆空間操作字符串
- 內存處理函數
- 內存分頁
- 內存模型
- 棧
- 棧溢出攻擊
- 內存泄露
- 大小端存儲法
- 寄存器
- 結構體
- 共用體
- 枚舉
- 文件操作
- 文件到底是什么
- 文件打開和關閉
- 文件的順序讀寫
- 文件的隨機讀寫
- 文件復制
- FILE和緩沖區
- 文件大小
- 插入,刪除,更改文件內容
- typeid
- 內部鏈接和外部鏈接
- 動態庫
- 調試器
- 調試的概念
- vs調試
- 多文件編程
- extern關鍵字
- 頭文件規范
- 標準庫以及標準頭文件
- 頭文件只包含一次
- static
- 多線程
- 簡介
- 創建線程threads.h
- 創建線程pthread
- gdb
- 簡介
- mac使用gdb
- setjump和longjump
- 零拷貝
- gc
- 調試器原理
- c++
- c++簡介
- c++對c的擴展
- ::作用域運算符
- 名字控制
- cpp對c的增強
- const
- 變量定義數組
- 盡量以const替換#define
- 引用
- 內聯函數
- 函數默認參數
- 函數占位參數
- 函數重載
- extern "C"
- 類和對象
- 類封裝
- 構造和析構
- 深淺拷貝
- explicit關鍵字
- 動態對象創建
- 靜態成員
- 對象模型
- this
- 友元
- 單例
- 繼承
- 多態
- 運算符重載
- 賦值重載
- 指針運算符(*,->)重載
- 前置和后置++
- 左移<<運算符重載
- 函數調用符重載
- 總結
- bool重載
- 模板
- 簡介
- 普通函數和模板函數調用
- 模板的局限性
- 類模板
- 復數的模板類
- 類模板作為參數
- 類模板繼承
- 類模板類內和類外實現
- 類模板和友元函數
- 類模板實現數組
- 類型轉換
- 異常
- 異常基本語法
- 異常的接口聲明
- 異常的棧解旋
- 異常的多態
- 標準異常庫
- 自定義異常
- io
- 流的概念和類庫結構
- 標準io流
- 標準輸入流
- 標準輸出流
- 文件讀寫
- STL
- 簡介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair對組
- 深淺拷貝問題
- 使用時機
- 常用算法
- 函數對象
- 謂詞
- 內建函數對象
- 函數對象適配器
- 空間適配器
- 常用遍歷算法
- 查找算法
- 排序算法
- 拷貝和替換算法
- 算術生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各種插件
- utf8編碼
- 制作安裝項目
- 編譯模式
- 內存對齊
- 快捷鍵
- 自動補全
- 查看c++類內存布局
- FFmpeg
- ffmpeg架構
- 命令的基本格式
- 分解與復用
- 處理原始數據
- 錄屏和音
- 濾鏡
- 水印
- 音視頻的拼接與裁剪
- 視頻圖片轉換
- 直播
- ffplay
- 常見問題
- 多媒體文件處理
- ffmpeg代碼結構
- 日志系統
- 處理流數據
- linux
- 系統調用
- 常用IO函數
- 文件操作函數
- 文件描述符復制
- 目錄相關操作
- 時間相關函數
- 進程
- valgrind
- 進程通信
- 信號
- 信號產生函數
- 信號集
- 信號捕捉
- SIGCHLD信號
- 不可重入函數和可重入函數
- 進程組
- 會話
- 守護進程
- 線程
- 線程屬性
- 互斥鎖
- 讀寫鎖
- 條件變量
- 信號量
- 網絡
- 分層模型
- 協議格式
- TCP協議
- socket
- socket概念
- 網絡字節序
- ip地址轉換函數
- sockaddr數據結構
- 網絡套接字函數
- socket模型創建流程圖
- socket函數
- bind函數
- listen函數
- accept函數
- connect函數
- C/S模型-TCP
- 出錯處理封裝函數
- 多進程并發服務器
- 多線程并發服務器
- 多路I/O復用服務器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反應堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函數
- libevent
- libevent簡介