Linux中線程創建,線程退出,線程接合

線程的簡單了解

之前我們了解過 task_struct 是用于描述進程的核心數據結構。它包含了一個進程的所有重要信息,并且在進程的生命周期內保持更新。我們想要獲取進程相關信息往往從這里得到。

  • 在Linux中,線程的實現方式與進程類似,每個線程都有一個task_struct結構體,用于存儲線程的信息。線程的task_struct結構體比進程的task_struct結構體要小,包含的信息更少。
  • 進程是操作系統資源分配的最小單位,而線程是操作系統調度的最小單位。
  • 線程之間的切換通常比進程切換更高效,因為線程共享進程的資源,不需要像進程切換那樣保存和恢復大量的資源信息。

windows中的線程和Linux中線程區別?

在Windows操作系統,內核中有真線程,名為TCB :線程控制塊。需要維護進程與線程之間的調度關系算法,這過于復雜。

?在Linux中,由于線程的控制塊與進程控制塊相似性非常高,所以直接復用了PCB的結構體——task_struct ,用PCB模擬線程的TCB。所以Linux沒有真正意義上的線程,而是用進程方案模擬的線程。這樣做的好處是復用代碼和結構更簡單,好維護,效率更高,也更安全。

線程的特性?

多線程的優點

  • 同時執行多個任務: 多線程允許程序同時執行多個任務,而不是按順序一個接一個地執行。
  • 提高響應速度: 對于需要處理大量并發請求的程序(如Web服務器),多線程可以顯著提高程序的響應速度。
  • 更小開銷,更快的切換: 線程切換的開銷也比進程切換要小,這使得多線程程序可以更高效地進行任務切換。
  • 在等待慢速I/O操作結束的同時,程序可執行其他的計算任務
  • 計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現,可以有效提高計算效率,但是注意:線程不是越多越好,正常情況下最合適的原則是:進程/線程與cpu個數/核數保持一致

多線程的缺點

  • 共享資源競爭 多個線程共享進程的地址空間,當它們同時訪問和修改共享資源時,可能會出現競爭條件,導致數據不一致或程序錯誤。
  • 同步機制復雜 為了解決線程安全問題,需要使用線程同步機制(如互斥鎖、條件變量等),這些機制會增加編程的復雜性,容易出錯。
  • 上下文切換開銷: 線程切換需要保存和恢復線程的上下文,這會消耗一定的CPU時間。過多的線程切換可能會降低程序的效率。
  • 線程間依賴: 線程之間可能存在依賴關系,一個線程的執行可能會影響到其他線程的執行。如果處理不當,可能會導致程序出現意外錯誤。
  • 調試困難:多線程程序的執行順序是不確定的,這使得程序的調試變得更加困難。由于線程的執行受到多種因素的影響,一些錯誤可能很難復現,增加了調試的難度。

PROSIX線程庫

與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以pthread_打頭的
要使用這些函數庫,要通過引入頭文件?<pthread.h>

并且鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項

之前我們使用的都是linux中的基礎標準庫,這些標準庫在編譯的時候會自動幫我們進行鏈接,我們只需要包含一個頭文件,不需要手動鏈接。但是對于線程庫的話默認不會幫我們鏈接,除了需要我們程序中包含對應頭文件,還需要編譯的時候手動鏈接

包含頭文件和編譯時鏈接區分

  • 包含頭文件(#include): 這是在源代碼文件中做的,用于告訴編譯器程序中使用了哪些函數、變量、類型等。頭文件通常包含函數聲明、宏定義、結構體定義等。
  • 編譯時鏈接: 這是在編譯命令中做的,用于告訴鏈接器將程序中使用的函數和變量與它們在庫文件中的具體實現鏈接起來。庫文件通常包含編譯好的函數和變量的二進制代碼。

包含頭文件的作用:

  • 讓編譯器理解代碼: 頭文件相當于一個“接口說明書”,告訴編譯器程序中使用了哪些“零件”(函數、變量等),以及這些“零件”的規格(參數類型、返回值類型等)。
  • 提供類型檢查: 編譯器可以根據頭文件中的聲明來檢查程序中函數和變量的使用是否正確,避免類型錯誤。

編譯時鏈接的作用:

  • 生成可執行文件: 鏈接器將程序中使用的函數和變量與它們在庫文件中的實現“組裝”起來,生成最終的可執行文件。
  • 鏈接外部代碼: 程序中使用的某些函數和變量可能不是由自己編寫的,而是由其他人或組織提供的,這些代碼通常放在庫文件中。鏈接器將這些外部代碼鏈接到程序中,使得程序可以使用這些外部功能。

線程創建

pthread_create函數介紹

函數作用:創建一個新的線程。這個線程在創建后會并行執行指定的線程函數

頭文件:#include <pthread.h>

函數原型

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);

參數

  • thread: 一個指向 pthread_t 類型變量的指針,用于存儲新創建線程的 ID
  • attr: 一個指向 pthread_attr_t 類型變量的指針,用于設置新線程的屬性。如果設置為 NULL,則使用默認屬性。通常我們設置成NULL就行
  • start_routine: 一個函數指針,表示新線程要執行的函數。該函數必須接受一個 void * 類型的參數,并返回一個 void * 類型的值。
  • arg: 一個指向 void * 類型變量的指針,表示傳遞給 start_routine 線程函數的參數。如果我們不需要傳遞任何數據給線程函數,完全可以將它設置為 NULL

返回值

  • 成功: 返回 0。
  • 失敗: 返回一個非零的錯誤碼,表示創建線程失敗的原因。

線程函數的定義

線程函數必須符合 void *(*start_routine)(void *) 的函數簽名,即接收一個 void * 類型的參數并返回一個 void * 類型的值。

簡單的線程創建例子

例子比較簡單,主要就是創建了一個新的線程,然后主線程和新線程同時執行,主線程輸出26個英文字母,新線程輸出數字0-9。

編譯代碼的時候記得加上編譯鏈接選項:-lpthread

gcc a.c -o a -lpthread
#include <stdio.h>
#include <pthread.h>void *thread_function(void *arg) {int i=0;while(1){fprintf(stderr,"%d",i);i++;if(i==10){i=0;}}
}int main() {pthread_t thread_id;int ret = pthread_create(&thread_id, NULL, thread_function, NULL);if (ret != 0) {perror("pthread_create failed");return 1;}printf("Thread created successfully\n");int i=0;while(1){fprintf(stderr,"%c",'a'+i);i++;if(i==26){i=0;}}return 0;
}

最終看到的效果就是主線程和新線程雙線執行輸出。

線程間共享資源與不共享資源

共享資源

  • 堆(Heap): 存儲進程中動態分配的對象。
  • 代碼段(Code Segment): 存儲程序的指令。
  • 數據段(Data Segment): 存儲進程的全局變量和靜態變量
  • 文件描述符表: 存儲進程打開文件的信息。
  • 信號處理函數: 用于處理進程接收到的信號。

不共享資源

  • 棧(Stack): 每個線程都有自己的棧,用于存儲局部變量、函數調用信息等。
  • 寄存器(Registers): 每個線程都有一組寄存器,用于存儲線程執行過程中的臨時數據。
  • 線程 ID: 每個線程都有一個唯一的線程 ID,用于標識線程。

使用共享資源時需要注意的問題:

  • 競態條件(Race Condition): 多個線程同時訪問和修改共享資源時,可能會導致數據不一致的問題。
  • 死鎖(Deadlock): 多個線程互相等待對方釋放資源,導致程序無法繼續執行的問題。

解決競態條件和死鎖問題的方法:

  • 互斥鎖(Mutex): 用于保護共享資源,同一時刻只允許一個線程訪問。
  • 條件變量(Condition Variable): 用于線程之間的同步,當一個線程等待某個條件滿足時,可以使用條件變量進行阻塞。
  • 信號量(Semaphore): 用于控制同時訪問共享資源的線程數量。

多線程共享資源同時訪問出錯例子

場景假設有一個共享的計數器變量 counter,初始值為 0。現在有多個線程同時對 counter 進行加 1 操作。

預期結果由于有 10 個線程,每個線程執行 100000 次加 1 操作,因此最終的 counter 值應該為 10 * 100000 = 1000000。

實際結果實際運行結果通常會小于 10000。

原因分析

當多個線程同時訪問 counter 變量時,由于線程切換的存在,可能會導致以下情況:

  1. 線程 A 讀取 counter 的值。
  2. 線程 A 被切換出去,線程 B 開始執行。
  3. 線程 B 讀取 counter 的值。
  4. 線程 B 將 counter 的值加 1。
  5. 線程 B 被切換出去,線程 A 繼續執行。
  6. 線程 A 將之前讀取的 counter 值加 1,并寫回。

這樣,線程 A 和線程 B 都只進行了一次加 1 操作,但 counter 的值只增加了 1,而不是 2。這種情況稱為競態條件

解決方法可以使用互斥鎖(Mutex)來保護共享資源 counter,確保同一時刻只有一個線程可以訪問它。

通過使用互斥鎖,可以保證每個線程對 counter 的加 1 操作都是原子性的,從而避免競態條件,得到正確的結果。

#include <stdio.h>
#include <pthread.h>#define NUM_THREADS 10
#define INCREMENTS 100000int counter = 0;void *increment_counter(void *arg) {for (int i = 0; i < INCREMENTS; i++) {counter++;}return NULL;
}int main() {pthread_t threads[NUM_THREADS];for (int i = 0; i < NUM_THREADS; i++) {pthread_create(&threads[i], NULL, increment_counter, NULL);}//作用是等待多個線程執行結束。它通常出現在多線程程序中,//用于確保主線程在所有子線程完成任務后才退出。for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}printf("Expected counter value: %d\n", NUM_THREADS * INCREMENTS);printf("Actual counter value: %d\n", counter);return 0;
}

線程退出?

pthread_exit 函數介紹

?函數作用:結束調用該函數的線程,同時還可以傳遞一個退出狀態值給其他線程

頭文件:#include <pthread.h>

函數原型

void pthread_exit(void *retval);

參數

retval 參數可以用于傳遞一個退出狀態值給其他線程,通常通過 pthread_join() 函數來接收這個值。如果不需要傳遞退出狀態,可以將 retval 設置為 NULL。

僵尸線程

首先我們來回顧一下僵尸進程:

僵尸進程

  • 當一個進程結束運行時,內核不會立即釋放它占用的所有資源,而是將其狀態設置為僵尸態(Zombie)
  • 僵尸進程會保留一些基本信息(如進程ID、退出狀態等),以便父進程可以獲取到子進程的退出信息。
  • 父進程需要調用 wait()waitpid() 等函數來回收僵尸進程的資源,否則僵尸進程會一直存在,占用系統資源。

然后我們來看一下線程的僵尸態

與進程類似,當一個線程結束運行時,它也會進入一個類似于僵尸態的狀態。

  • 狀態保留: 線程退出后,其占用的大部分資源(如棧空間)會被自動回收,但是線程也會保留一些狀態信息,例如退出狀態,以便其他線程(通常是主線程)可以通過?pthread_join() 函數來獲取。
    ?
  • 回收方式: 線程的“回收”主要通過 pthread_join() 函數來實現。當主線程調用 pthread_join() 函數等待某個線程結束時,實際上就是在“回收”該線程的狀態信息。

僵尸態的重要性?

  • 無論是進程還是線程,僵尸態的存在都是為了讓父進程或主線程能夠獲取到子進程或子線程的退出信息
  • 這些退出信息可能包含執行結果、錯誤碼等,對于程序的調試和錯誤處理非常有幫助。

線程接合

pthread_join函數介紹

?函數作用:阻塞當前線程,直到指定的線程執行完畢,適用于線程間的同步

頭文件:#include <pthread.h>

函數原型

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

參數

thread:要等待的線程的線程 ID。這是一個由 pthread_create 創建的線程 ID。

retval:這是一個指向指針的指針,函數會把目標線程的退出狀態通過該指針返回。如果目標線程沒有返回任何值,可以傳遞 NULL。

其實我蠻不理解這里為什么使用二級指針的,在我看來pthread_exit 傳遞的參數是一個一級指針,但是這里pthread_join選擇一個一級指針來對應賦值就可以了,不太理解為什么要使用二級指針

?

返回值

  • 成功: 返回 0。
  • 失敗: 返回一個非零的錯誤碼。

適用場景

  • 同步線程: 當一個線程需要等待另一個線程完成后才能繼續執行時,可以使用 pthread_join() 函數進行同步。
  • 獲取線程返回值: 有些線程會返回一個值,表示它們的執行結果。可以使用 pthread_join() 函數獲取這個返回值。
  • 資源回收: 當一個線程結束后,它的資源不會立即被釋放。需要調用 pthread_join() 函數才能回收這些資源。

?線程分離態(了解)

線程的默認狀態

默認情況下,新創建的線程都處于非分離態 (Joinable State)。這意味著:

  1. 資源回收: 當一個線程結束運行時,它所占用的資源(如棧空間)不會立即被釋放,而是會保留一段時間,直到有其他線程調用 pthread_join() 函數來“回收”該線程。
  2. 獲取退出狀態: 其他線程可以通過調用 pthread_join() 函數來等待該線程結束,并獲取它的退出狀態。

什么是分離態?

分離態 (Detached State) 是一種特殊的線程狀態。當一個線程被設置為分離態時,它與創建它的線程(通常是主線程)之間的關系就會被“分離”。這意味著:

  1. 自動資源回收: 當一個分離態線程結束運行時,它所占用的資源會被自動回收,無需其他線程調用 pthread_join() 函數。
  2. 無法獲取退出狀態: 其他線程無法通過 pthread_join() 函數來等待分離態線程的結束,也無法獲取它的退出狀態。

如何設置線程為分離態?

方法一

pthread_detach(thread_id);

這個函數可以直接將指定線程設置為分離狀態。

但是你可能會想如何獲得一個線程自身的線程tid呢?其實很簡單,有一個函數pthread_self可以很容易幫我們獲取到當前線程的tid。

這個函數在后面一篇文章中也會詳細講到,這里只是簡單提一下。

函數原型:pthread_t pthread_self(void);

所以我們常常將 pthread_self 配合 pthread_detach 一起使用,像下面這樣:

pthread_detach(pthread_self());

方法二

使用pthread_create函數創建線程的時候,有一個參數可以設置新創建的線程的屬性。我們可以憑借這個參數來設置線程為分離態,這種方式相比于方法一,更加麻煩,但是有著自己的優點,之后我們會詳細講到,這里不詳細闡述。

適用場景

有些情況下,我們對于某些線程來說不關心它的返回狀態,并且也不想要使用pthread_join來阻塞等待回收這個死后的僵尸線程。那么此時我們就可以把這個線程設置成分離態度,當線程死亡自動釋放,不需要其他線程調用pthread_join來回收這個僵尸進程的資源。

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

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

相關文章

HarmonyOS:使用List實現分組列表(包含粘性標題)

一、支持分組列表 在列表中支持數據的分組展示&#xff0c;可以使列表顯示結構清晰&#xff0c;查找方便&#xff0c;從而提高使用效率。分組列表在實際應用中十分常見&#xff0c;如下圖所示聯系人列表。 聯系人分組列表 在List組件中使用ListItemGroup對項目進行分組&#…

django上傳文件

1、settings.py配置 # 靜態文件配置 STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR /static, ]上傳文件 # 定義一個視圖函數&#xff0c;該函數接收一個 request 參數 from django.shortcuts import render # 必備引入 import json from django.views.decorators.http i…

【前端知識】瀏覽器兼容方案polyfill

瀏覽器兼容方案polyfill 什么是 Polyfill&#xff1f;Polyfill 的作用Polyfill 的工作原理1. **特性檢測**2. **加載 Polyfill**3. **模擬實現** Polyfill 的常見場景Polyfill 的使用方式Polyfill 的優缺點優點缺點 常見的 Polyfill 庫總結 什么是 Polyfill&#xff1f; Polyf…

C#學習之DateTime 類

目錄 一、DateTime 類的常用方法和屬性的匯總表格 二、常用方法程序示例 1. 獲取當前本地時間 2. 獲取當前 UTC 時間 3. 格式化日期和時間 4. 獲取特定部分的時間 5. 獲取時間戳 6. 獲取時區信息 三、總結 一、DateTime 類的常用方法和屬性的匯總表格 在 C# 中&#x…

dedecms 開放重定向漏洞(附腳本)(CVE-2024-57241)

免責申明: 本文所描述的漏洞及其復現步驟僅供網絡安全研究與教育目的使用。任何人不得將本文提供的信息用于非法目的或未經授權的系統測試。作者不對任何由于使用本文信息而導致的直接或間接損害承擔責任。如涉及侵權,請及時與我們聯系,我們將盡快處理并刪除相關內容。 0x0…

如何選擇合適的超參數來訓練Bert和TextCNN模型?

選擇合適的超參數來訓練Bert和TextCNN模型是一個復雜但關鍵的過程&#xff0c;它會顯著影響模型的性能。以下是一些常見的超參數以及選擇它們的方法&#xff1a; 1. 與數據處理相關的超參數 最大序列長度&#xff08;max_length&#xff09; 含義&#xff1a;指輸入到Bert模…

AWS 前端自動化部署流程指南

本文詳細介紹從前端代碼開發到 AWS 自動化部署的完整流程。 一、流程概覽 1.1 部署流程圖 #mermaid-svg-nYg7k6L5IKVBjDtr {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nYg7k6L5IKVBjDtr .error-icon{fill:#552…

Office word打開加載比較慢處理方法

1.添加safe參數 ,找到word啟動項,右擊word,選擇屬性 , 添加/safe , 應用并確定 2.取消加載項,點擊文件,點擊選項 ,點擊加載項,點擊轉到,取消所有勾選,確定。

大數據SQL調優專題——Spark執行原理

引入 在深入MapReduce中有提到&#xff0c;MapReduce雖然通過“分而治之”的思想&#xff0c;解決了海量數據的計算處理問題&#xff0c;但性能還是不太理想&#xff0c;這體現在兩個方面&#xff1a; 每個任務都有比較大的overhead&#xff0c;都需要預先把程序復制到各個 w…

MYSQL下載安裝及使用

MYSQL官網下載地址&#xff1a;https://downloads.mysql.com/archives/community/ 也可以直接在服務器執行指令下載&#xff0c;但是下載速度比較慢。還是自己下載好拷貝過來比較快。 wget https://dev.mysql.com/get/Downloads/mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz 1…

CentOS 7.8 安裝MongoDB 7 副本集(Replica Set)

文章目錄 1 環境假設步驟1&#xff1a;在兩臺服務器上安裝MongoDB步驟2&#xff1a;配置副本集步驟3&#xff1a;初始化副本集步驟4&#xff1a;驗證副本集配置步驟5&#xff1a;設置安全性&#xff08;可選&#xff09;擴展配置示例&#xff1a;最佳實踐&#xff1a;仲裁節點步…

AJAX 與 ASP 的深入探討

AJAX 與 ASP 的深入探討 引言 隨著互聯網技術的飛速發展,Web應用程序的交互性和性能要求越來越高。AJAX(Asynchronous JavaScript and XML)和ASP(Active Server Pages)作為兩種重要的Web開發技術,在提高Web應用程序性能和用戶體驗方面發揮著重要作用。本文將深入探討AJ…

內網下,Ubuntu (24.10) 離線安裝docker最新版教程

一般在數據比較敏感的情況下&#xff0c;是無法使用網絡的&#xff0c;而對于Ubuntu系統來說&#xff0c;怎么離線安裝docker呢&#xff1f; 下面我給大家來講一下&#xff1a; 采用二進制安裝&#xff1a; 1.下載docker離線包 官網下載&#xff1a; Index of linux/static…

Copilot Next Edit Suggestions(預覽版)

作者&#xff1a;Brigit Murtaugh&#xff0c;Burke Holland 排版&#xff1a;Alan Wang 我們很高興向你介紹在本次 Visual Studio Code 發布中&#xff0c;關于 GitHub Copilot 的三個預覽功能&#xff1a; Next Edit Suggestions&#xff08;NES&#xff09;Copilot Edits 的…

高性能內存對象緩存Memcached詳細實驗操作

目錄 前提準備&#xff1a; cache1&#xff0c;2&#xff1a; 客戶端cache-api&#xff08;一定得是LAMP環境&#xff09; memcache實現主主復制以及高可用(基于以上完成) cache1,2: memcachekeepalived(基于以上完成) cache1,2: 前提準備&#xff1a; 1. 準備三臺cent…

全單模矩陣及其在分支定價算法中的應用

全單模矩陣及其在分支定價算法中的應用 目錄 全單模矩陣的定義與特性全單模矩陣的判定方法全單模矩陣在優化中的核心價值分支定價算法與矩陣單模性的關系非全單模問題的挑戰與系統解決方案總結與工程實踐建議 1. 全單模矩陣的定義與特性 關鍵定義 單模矩陣&#xff08;Unimo…

Spring AI發布!讓Java緊跟AI賽道!

1. 序言 在當今技術發展的背景下&#xff0c;人工智能&#xff08;AI&#xff09;已經成為各行各業中不可忽視的重要技術。無論是在互聯網公司&#xff0c;還是傳統行業&#xff0c;AI技術的應用都在大幅提升效率、降低成本、推動創新。從智能客服到個性化推薦&#xff0c;從語…

【kafka系列】Kafka如何保證消息不丟失?

目錄 1. 生產者端&#xff1a;確保消息成功發送到Broker 核心機制&#xff1a; 關鍵步驟&#xff1a; 2. Broker端&#xff1a;持久化與副本同步 核心機制&#xff1a; 關鍵源碼邏輯&#xff1a; 3. 消費者端&#xff1a;可靠消費與Offset提交 核心機制&#xff1a; 關…

利用二分法+布爾盲注、時間盲注進行sql注入

一、布爾盲注&#xff1a; import requestsdef binary_search_character(url, query, index, low32, high127):while low < high:mid (low high 1) // 2payload f"1 AND ASCII(SUBSTRING(({query}),{index},1)) > {mid} -- "res {"id": payloa…

UART(一)——UART基礎

一、定義 UART(Universal Asynchronous Receiver/Transmitter)是一種廣泛使用的串行通信協議,用于在設備間通過異步方式傳輸數據。它無需共享時鐘信號,而是依賴雙方預先約定的參數(如波特率)完成通信。 功能和特點 基本的 UART 系統只需三個信號即可提供穩健的中速全雙工…