【Linux系統編程學習】Linux線程控制原語

此為牛客Linux C++課程筆記。

0. 關于線程

在這里插入圖片描述
注意:LWP號和線程id不同, LWP號是CPU分配時間片的依據,線程id是用于在進程內部區分線程的。

1. 線程與進程的區別

在這里插入圖片描述
在這里插入圖片描述
對于進程來說,相同的地址(同一個虛擬地址)在不同的進程中,反復使用而不沖突。原因是他們雖虛擬址一樣,但,頁目錄、頁表、物理頁面各不相同。相同的虛擬址,映射到不同的物理頁面內存單元,最終訪問不同的物理頁面。

但!線程不同!兩個線程具有各自獨立的PCB,但共享同一個頁目錄,也就共享同一個頁表和物理頁面。所以兩個PCB共享一個地址空間。

實際上,無論是創建進程的fork,還是創建線程的pthread_create,底層實現都是調用同一個內核函數clone。
如果復制對方的地址空間,那么就產出一個“進程”;如果共享對方的地址空間,就產生一個“線程”。

因此:Linux內核是不區分進程和線程的。只在用戶層面上進行區分。所以,線程所有操作函數 pthread_* 是庫函數,而非系統調用。

優點: 1. 提高程序并發性 2. 開銷小 3. 數據通信、共享數據方便
缺點: 1. 庫函數,不穩定 2. 調試、編寫困難、gdb不支持 3. 對信號支持不好
優點相對突出,缺點均不是硬傷。Linux下由于實現方法導致進程、線程差別不是很大。

2. 線程相關操作函數

在這里插入圖片描述

2.1 獲取線程id

#include <pthread.h>
pthread_t pthread_self(void);

功能:獲取線程ID。其作用對應進程中 getpid() 函數。

2.2 創建線程: pthread_create

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

功能:創建一個子線程

參數:

  • thread:傳出參數,線程創建成功后,子線程的線程ID被寫到該變量中。
  • attr : 設置線程的屬性,一般使用默認屬性,即NULL
  • start_routine : 函數指針,這個函數是子線程需要處理的邏輯代碼
  • arg : 給第三個參數(回調函數)使用,是回調函數的參數

返回值:

  • 成功:0
  • 失敗:返回錯誤號。這個錯誤號和之前errno不太一樣,獲取錯誤號的信息使用:
#include <string.h>
char * strerror(int errnum);

創建線程示例代碼如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* callback(void* arg) {printf("its child thread, thread id is %lu\n", pthread_self());printf("arg = %d\n", *(int *)arg);
}int main()
{pthread_t pid;int a = 5;int ret = pthread_create(&pid, NULL, callback, &a);if(ret != 0) {// 說明創建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("its main thread, thread id is %lu\n", pthread_self());sleep(1);return 0;
}

發現無法編譯
在這里插入圖片描述
查閱文檔發現:
在這里插入圖片描述
編譯時加-pthread即可,運行結果如下:
在這里插入圖片描述

2.3 終止線程: pthread_exit

注意,不能使用exit函數終止當前線程,exit將終止當前進程,進程中的所有線程將一并終止。

#include <pthread.h>
void pthread_exit(void *retval);

參數:retval表示線程退出狀態,通常傳NULL

多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,將單個線程退出。任何線程里exit導致進程退出,其他線程未工作結束,主控線程退出時不能return或exit。

2.4 連接已終止的線程(回收線程):pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

功能:和一個已經終止的線程進行連接(回收子線程的資源)

注意:這個函數是阻塞函數,調用一次只能回收一個子線程,一般在主線程中使用

參數:

  • thread:需要回收的子線程的ID
  • retval: 接收子線程退出時的返回值(即pthread_exit的void *retval參數), 而且是傳出參數。

返回值:0 : 成功;非0 : 失敗,返回的錯誤號

不使用傳出參數的一個簡單使用如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* callback(void* arg) {printf("子線程運行中...\n");sleep(2);
}int main()
{pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {// 說明創建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_join(pid, NULL);printf("子線程已回收\n");return 0;
}

子線程執行2秒后,主進程才輸出“子線程已回收”,說明pthread_join函數是阻塞的。

pthread_join函數比較難以理解的地方是他的第二個參數:void **retval,是void二級指針類型,這是因為:

首先這個參數是想接收pthread_exit所傳出的void *retval, 這個參數本身是void *的一級指針類型,而pthread_join函數的void **retval在設計時是設計成一個傳出參數的,以便把pthread_exit傳出的void *retval帶回主線程,所以要想把 void * 類型變量設計成傳出參數,即是 void **。

示例程序如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>int value = 10;void* callback(void* arg) {printf("子線程運行中...\n");pthread_exit((void *)&value);
}int main()
{pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {// 說明創建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}int *thread_retval;  // 給pthread_join調用,接收pthread_exit的傳出參數pthread_join(pid, (void **)&thread_retval);printf("exit data : %d\n", *thread_retval);return 0;
}

運行結果如下:
在這里插入圖片描述

2.5 線程分離:pthread_detach

#include <pthread.h>
int pthread_detach(pthread_t thread);

功能:使進程處于分離狀態。被分離的線程在終止的時候,會自動釋放資源返回給系統,避免產生僵尸線程。

線程分離狀態:指定該狀態,線程主動與主控線程斷開關系。線程結束后,其退出狀態不由其他線程獲取,而直接自己自動釋放。網絡、多線程服務器常用。

參數:需要分離的線程的ID

返回值:成功:0,失敗:返回錯誤號

注意:

  1. 線程不能多次分離,會產生不可預料的行為。
  2. 不能去連接(pthread_join)一個已經分離的線程,會報錯:一般情況下,線程終止后,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。不能對一個已經處于detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL錯誤。也就是說,如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

2.6 線程取消:pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);

功能:取消線程(讓線程終止)

【注意】:線程的取消并不是實時的,而有一定的延時。需要等待線程到達某個取消點(檢查點)。
類似于玩游戲存檔,必須到達指定的場所(存檔點,如:客棧、倉庫、城里等)才能存儲進度。殺死線程也不是立刻就能完成,必須要到達取消點。
取消點:是線程檢查是否被取消,并按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write…
執行命令man 7 pthreads可以查看具備這些取消點的系統調用列表。也可參閱 APUE.12.7 取消選項小節。
可粗略認為一個系統調用(進入內核)即為一個取消點。如線程中沒有取消點,可以通過調用pthreestcancel函數自行設置一個取消點。

看下面這個代碼示例,子線程無限循環:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* callback(void* arg) {while(1) {printf("子線程運行中...\n");sleep(1);}return NULL;
}int main()
{pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_cancel(pid);ret = pthread_join(pid, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("線程已回收\n");return 0;
}

運行后成功輸出”線程已回收“, 這是因為pthread_cancel終止了子線程的運行,故pthread_join得以執行。

但是如果將子進程中循環語句中的內容去掉:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* callback(void* arg) {while(1) {// printf("子線程運行中...\n");// sleep(1);}return NULL;
}int main()
{pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_cancel(pid);ret = pthread_join(pid, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("線程已回收\n");return 0;
}

運行以后發現沒有輸出,主線程阻塞。這是因為子線程的while(1)死循環中沒有任何語句,也就不會執行任何系統調用,也就不會到達任何一個“取消點”,所以子線程并沒有被終止,主線程被阻塞在pthread_join處。而之前的代碼循環語句中的printf會調用系統調用write,所以會到達“取消點”,pthread_join將已經結束的子線程回收。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384559.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384559.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384559.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Linux網絡編程學習】預備知識(網絡字節序、IP地址轉換函數、sockaddr數據結構)

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 網絡字節序 我們已經知道&#xff0c;內存中的多字節數據相對于內存地址有大端和小端之分。 磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分。網絡數據流同樣有大端小端之分&#xff0c;那么如何定義網絡數…

【Linux網絡編程學習】socket API(socket、bind、listen、accept、connect)及簡單應用

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 什么是socket 所謂 socket&#xff08;套接字&#xff09;&#xff0c;就是對網絡中不同主機上的應用進程之間進行雙向通信的端點的抽象。 一個套接字就是網絡上進程通信的一端&#xff0c;提供了應用層進程利用網絡協議交換…

【Linux網絡編程學習】使用socket實現簡單服務器——多進程多線程版本

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 多進程版 1.1 思路 大體思路與上一篇的單進程版服務器–客戶端類似&#xff0c;都是遵循下圖&#xff1a; 多進程版本有以下幾點需要注意&#xff1a; 由于TCP是點對點連接&#xff0c;服務器主進程連接了一個客戶端以后…

【Linux網絡編程學習】I/O多路復用——select和poll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 0. I/O多路復用 所謂I/O就是對socket提供的內存緩沖區的寫入和讀出。 多路復用就是指程序能同時監聽多個文件描述符。 之前的學習中寫了多進程和多線程版的簡單服務器模型&#xff0c;但是有個問題&#xff1a;每次新來一個客…

【Linux網絡編程學習】I/O多路復用——epoll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 關于epoll epoll是Linux下多路復用IO接口select/poll的增強版本&#xff0c;它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率&#xff0c;因為它會復用文件描述符集合來傳遞結果而不用迫使開發者每次…

【Linux網絡編程學習】阻塞、非阻塞、同步、異步以及五種I/O模型

文章目錄1. 基本概念1.1 阻塞與非阻塞1.2 同步與異步1.3 為什么沒有“異步阻塞”2. 五種IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO復用&#xff08;IO multiplexing&#xff09;2.4 信號驅動&#xff08;signal-driven&#xff09;2.5 異步&#xff08;asynchron…

LRU緩存 數據結構設計(C++)

做LeetCode第146題LRU緩存&#xff0c;覺得收獲不小&#xff0c;特此記錄。 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存。int get(int key) 如果關鍵…

STM32時鐘樹解析

本人之前其實也用STM32做過一些小東西&#xff0c;但因為時鐘的初始化一般是直接在SystemInit時鐘系統初始化函數里直接配置為72MHz&#xff0c;所以對于STM32的時鐘框圖并沒有怎么理會&#xff0c;今天剛好有空就重新看了一下并寫一篇博客記錄一下吧&#xff0c;以免以后又忘了…

S3C2440時鐘體系

S3C2440在默認情況下&#xff0c;整個系統全靠一個12MHz的外部晶振提供頻率來工作運行的&#xff0c;也就是說CPU、內存、UART、ADC等所有需要用到時鐘頻率的硬件都工作在12MHz下&#xff0c;但是通過查閱芯片手冊我們知道CPU時鐘最高可為400MHZ&#xff0c;那么怎么設置時鐘讓…

關于MCU、CPU擴展SDRAM的一個小知識

像上圖這種硬件電路圖上的16個數據位和我們在初始化SDRAM的時候設置的16位數據位寬是指我們讀寫SDRAM的時候可以同時讀寫16個數據位&#xff0c;數據線越多肯定越快&#xff0c;但是數據線也不可能無限增加&#xff0c;我們在程序里是可以讀寫8位&#xff0c;16位&#xff0c;3…

S3C2440擴展SDRAM

本文主要目的是記錄一下S3C2440擴展SDRAM的一些知識&#xff0c;方便以后查閱。 通過查閱手冊我們知道&#xff0c;2440有8個可以用來擴展內存的BANK&#xff0c;其中第6和第7還可用來擴展SDRAM 下面我們來看一下2440擴展SDRAM需要設置哪些寄存器。 一、BWSCON寄存器 該寄存器…

匯編語言的相對跳轉和絕對跳轉以及反匯編代碼解析

上圖第一行的b1 main為相對跳轉&#xff0c;即跳轉到pcoffset,其中pc為當前pc值&#xff0c;offset可以理解為偏移地址&#xff0c;也就是根據當前所在地址加上偏移地址實現跳轉&#xff0c;為相對跳轉。 我們來看看它的反匯編代碼 上圖清除完bss區后使用b1指令跳轉到30000668…

韋東山嵌入式第一期14課第004節_und異常模示程序示例_P筆記

本節課的第一個程序韋老師是想讓大家見識一下未定義異常&#xff0c;而第二個程序是對第一個程序進行改進&#xff0c;防止在某些條件下執行不了&#xff0c;下面就來講一下第2個程序改進了哪些地方并且有什么用。 程序在此路徑中&#xff1a;源碼文檔圖片\源碼\源碼_20180321…

關于NOR FLASH地址左右移的問題

問題引入&#xff1a;不知道你會不會有這樣的疑問&#xff1a;為什么在發送解鎖命令時&#xff0c;我們不用右移一位&#xff0c;而發送扇區地址時卻要右移一位&#xff08;nor_cmd函數內部已經左移一位&#xff09;&#xff0c;這里先補充說明一下說明是cpu角度和nor角度&…

在linux下利用ls命令進行模糊查找

如上圖&#xff0c;我們當前路徑下有三個文件&#xff0c;分別為helloworld.c以及helloworld和1.c&#xff0c;直接輸入命令ls則顯示所有文件&#xff0c;我們可以利用ls 加*的方向進行模糊查找。 輸入ls 目錄名 形式的命令行&#xff0c;則是對該目錄名下的文件全部進行顯示&a…

Makefile常見符號意思

Makefile里有許許多多的符號&#xff0c;對于新手而言如果沒有經常使用&#xff0c;就很容易忘記&#xff0c;所以我把常見符號的意義寫下&#xff0c;方便日后忘記查詢。本文章會持續更新... 1.$&#xff1a;代表目標&#xff1b;$^代表所有依賴&#xff0c;$^代表第一個依賴。…

Linux下串口通信詳解

https://blog.csdn.net/u010783226/article/details/73369097

fstat、stat和lstat 區別

nt fstat(int filedes, struct stat *buf); int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf); 一眼就能看出來fstat的第一個參數是和另外兩個不一樣的&#xff0c;fstat區別于另外兩個系統調用的地方在于&#xff0c;fstat系…

Linux的幀緩沖設備

Linux的幀緩沖設備 幀緩沖&#xff08;framebuffer&#xff09;是 Linux 為顯示設備提供的一個接口&#xff0c;把顯存抽象后的一種設備&#xff0c;他允許上層應用程序在圖形模式下直接對顯示緩沖區進行讀寫操作。這種操作是抽象的&#xff0c;統一的。用戶不必關心物理顯存的…