【Liunx專欄_6】Linux線程概念與控制

在這里插入圖片描述

目錄

  • 1、線程是什么?通過一個圖來理解……
  • 2、Linux進程和線程?
    • 2.1、之間的關系和區別
    • 2.2、線程的優缺點?
  • 3、線程的創建
    • 3.1、POSIX線程庫
    • 3.2、創建線程
    • 3.3、PS查看運行的線程
  • 4、線程的終止
  • 5、線程的等待
  • 6、線程分離
  • 7、線程封裝

1、線程是什么?通過一個圖來理解……

首先,我們知道進程等于PCB+自己的數據和代碼,在創建進程的時候,操作系統要對進程進行描述,則創建一個結構體,里面包含進程的所有屬性信息(標志符、狀態信息、優先級等……),該結構體在Linux中稱為“PCB”,即進程控制塊,在Litnux中結構體名叫task_strcut。該信息會存儲在程序地址空間中,也就是所說的虛擬地址空間。里面就存儲了該進程的所有信息和自己的代碼和數據信息。它們通過頁表進行映射到物理內存空間,也就是加載內存,進行CPU的調度執行。
上面描述的整個流程,從進程的創建(PCB的創建)、程序的代碼和數據通過頁表進行的映射關系的建立、到CPU的調度執行,該流程就可以看做是一個執行路線,只不過只有一個執行流,我么把該執行線路就稱之為線程
即線程就是一個進程中的一天控制序列。并且一個進程中至少有一個執行線程。
在這里插入圖片描述
之前描述的PCB只有一個,此處就稱為該進程有一個執行線程,但在一個進程中可以存在多個執行流,所有執行流會公用一個虛擬內存空間,在虛擬內存中,操作系統會將資源合理分配給多個執行流。

2、Linux進程和線程?

2.1、之間的關系和區別

  1. 進程:是系統資源分配的基本單位。
  2. 線程:是CPU調度執行的基本單位。
  3. 線程共享進程數據的同時,線程也有自己的一部分數據信息,用來描述不同線程。(線程ID、調度優先級、信號屏蔽字等……)
  4. 進程中的多線程共享:即在一個進程中,有一個程序地址空間,該空間被所有線程共享,因此Text Segment、Data Segment都是共享的,因此只要定義一個函數后,所有線程都是可以調用的,同時定義了一個全局變量后也是共享的,此外多線程之間還共享了文件描述符表、當前工作目錄、用戶id和組id等……
  5. 線程是進程的執行分支,只要一個線程出現異常情況,也就影響到整個進程,從而導致整個進程的崩潰,進而終止退出。

2.2、線程的優缺點?

  1. 線程的優點:

從創建的角度看:創建一個新線程比創建一個新進程的代價小的多,因為從上面的關系和區別可以看出,創建一個線程不需要從新分配虛擬地空間,沒有數據的大量拷貝,而創建進程需要創建新的程序地址空間,同時還要拷貝原始數據,增加的系統的消耗。

從切換的角度看:與進程間切換相比,線程間切換需要操作的工作量更小,主要區別就是線程之間的切換,虛擬地址空間是相同的。
從執行效率上看:在多線程的情況下可以實現并發執行,提高執行的效率。

  1. 線程的缺點:

主要是性能上,一個處理器上有密集型線程的數量進行執行的時候,會有較大的性能損失,會增加額外的同步和調度開銷。但是,合理的使?多線程,能提?CPU密集型程序的執?效率。

健壯性上:在多線程的情況下,由于虛擬地址空間是共享的,會造成一些空間的數據在同一時刻被多個線程訪問,即缺少資源的保護,因此在后面會提到互斥與信號量,用來應對多線程的情況下共享資源的多次訪問。

3、線程的創建

3.1、POSIX線程庫

  • 在Linux中,創建線程就會用到里面的庫,需要包含頭文件<pthread.h>。
  • 同時在鏈接這些線程函數庫的時候,編譯時需要加-lpthread選項。

3.2、創建線程

  1. 函數接口是:pthread_create()
    在這里插入圖片描述
  2. 在代碼中的使用:
#include<iostream>
#include<pthread.h>
#include<string.h>
#include<unistd.h>void *work(void *arg)
{while(1){std::cout<<"我是線程-1: "<<pthread_self()<<"……"<<std::endl;sleep(1);}
}int main()
{pthread_t tid;int ret=pthread_create(&tid,nullptr,work,nullptr);if(ret!=0){std::cout<<"create failed!,code_num:"<<strerror(ret)<<std::endl;exit(-1);}while(1){std::cout<<"我是主線程: "<<pthread_self()<<"……"<<std::endl;sleep(1);}return 0;
}

在這里插入圖片描述

  • 上面的代碼就是在主線程中創建一個新的線程,總共兩個線程,因此運行后有兩個執行流,整個程序稱為一個進程,每個線程有自己的唯一表示符號,上面使?是是 pthread_self(),得到的這個數實際上是?個地址,在虛擬地址空間上的?個地址,通過這個地址, 可以找到關于這個線程的基本信息,包括線程ID,線程棧,寄存器等屬性。即線程id。我們通過打印看到確實有兩個同時輸出在顯示器上,下面通過ps查看是不是真有兩個線程在運行。

3.3、PS查看運行的線程

while :; do ps -aL | head -1 && ps -aL | grep 可執行程序名 | grep -v grep ; echo "************" ; sleep 1 ; done循環監控查看線程情況。
在這里插入圖片描述
通過布局監控,運行程序可以看到,確實有兩個線程,它們的PID都是一樣的,說明這兩個線程擁有同一個父進程,看到LWP,其中一個和PID相同,說明該線程就是主線程,另一個就是創建的新線程。LWP 是什么呢?LWP 得到的是真正的線程ID

注意:
主線程的棧在虛擬地址空間的棧上,?其他線程的棧在是在共享區(堆棧之間),因為pthread系列函數都是pthread庫提供給我們的。?pthread庫是在共享區的。所以除了主線程之外的其他線程的棧都在共享區。

4、線程的終止

只終止其中的某個線程,而不是終止整個進程,有一下三種方法:

  1. 在創建的線程函數中調用return,注意不要在主線程中調用return,在主線程中調用retrun ,就相當于調用exit
void *work(void *arg)
{int num=5;while(num--){std::cout<<"我是線程-1: "<<pthread_self()<<"……"<<std::endl;sleep(1);}return nullptr;
}

在這里插入圖片描述

  1. 直接調用退出接口pthread_exit()終止線程。

在這里插入圖片描述
value_ptr:value_ptr不要指向?個局部變量

  1. 調用pthread_cancel()取消一個在執行的線程

在這里插入圖片描述
參數就是傳入線程id。成功返回0。

5、線程的等待

  • 為什么要等待?
    因為退出的線程,其空間沒有被釋放,依舊在進程地址空間中的。若創建新的線程并不會使用剛退出的線程的地址空間,因此就造成了浪費。

  • 等待的函數接口:pthread_join()

在這里插入圖片描述
thread:線程ID
value_ptr:它指向?個指針,后者指向線程的返回值,返回值會根據調用不同的終止接口返回不同的值。

  • 通過代碼演示:創建三個線程,調用不同的終止接口,看等待的返回接收參數值是什么……
#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void *thread1(void *arg)
{std::cout << "thread1 running……" << std::endl;int *p = (int *)malloc(sizeof(int));*p = 1;return (void *)p;
}void *thread2(void *arg)
{std::cout << "thread2 running……" << std::endl;int *p = (int *)malloc(sizeof(int));*p = 2;pthread_exit((void *)p);
}void *thread3(void *arg)
{while (1){std::cout << "thread3 running……" << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;void *ret;if (pthread_create(&tid, nullptr, thread1, nullptr) != 0){std::cout << "創建失敗……" << std::endl;exit(-1);}pthread_join(tid, &ret);printf("thread1 退出,thread1 id=%X,return code:%d\n", tid, *(int *)ret);free(ret);if (pthread_create(&tid, nullptr, thread2, nullptr) != 0){std::cout << "創建失敗……" << std::endl;exit(-1);}pthread_join(tid, &ret);printf("thread2 退出,thread2 id=%X,return code:%d\n", tid, *(int *)ret);free(ret);if (pthread_create(&tid, nullptr, thread3, nullptr) != 0){std::cout << "創建失敗……" << std::endl;exit(-1);}sleep(3);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("thread return, thread id=%X, return code:PTHREAD_CANCELED\n",tid);elseprintf("thread return, thread id=%X, return code:NULL\n", tid);while(1){std::cout<<"我是主線程……"<<std::endl;sleep(1);}return 0;
}

在這里插入圖片描述

  1. 如果thread線程通過return返回,value_ ptr所指向的單元?存放的是thread線程函數的返回值。
  2. 如果thread線程被別的線程調?pthread_ cancel異常終掉,value_ ptr所指向的單元?存放的是常數PTHREAD_ CANCELED。
  3. 如果thread線程是??調?pthread_exit終?的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
  4. 如果對thread線程的終?狀態不感興趣,可以傳NULL給value_ ptr參數。

6、線程分離

上面看到線程的等待,線程退出后需要對線程進行等待,保證資源的釋放,避免系統資源的泄露。可以看到通過等待線程進行資源的釋放不是太方便,因此線程創建在默認情況下是joinable的,也就是可以線程分離的,指將一個線程從其創建者(通常是主線程)中分離出來,使其成為一個獨立的執行實體。分離后的線程在終止時會自動釋放其資源,而不需要其他線程顯式地等待或回收它。

主要特點
1、資源自動回收:分離的線程在結束時系統會自動回收其資源
2、無需join:其他線程不需要調用join()或類似函數來等待分離線程結束
3、獨立性:分離后的線程運行獨立于創建它的線程

線程分離的接口函數:pthread_detach(線程ID)
下面代碼描述:即通過線程分離技術,讓線程自己執行5次后自動分離,自動釋放資源,分離后依然顯示調用join等到,依舊會等待的,只不過不會執行后面的代碼,因為是阻塞式等待的。

#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* thread1(void* arg)
{int num=5;while(num--){std::cout<<(char*)arg<<std::endl;sleep(1);}pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;if(pthread_create(&tid,nullptr,thread1,(void*)"thread1線程running……")!=0){std::cout<<"創建線程失敗……"<<std::endl;}sleep(1);if(pthread_join(tid,nullptr)==0){std::cout<<"等待成功……"<<std::endl;return 0;//不return 就會繼續執行后面的主線程}else{std::cout<<"等待失敗……"<<std::endl;return -1;}while(1){std::cout<<"主線程運行中……"<<std::endl;sleep(1);}return 0;
}

使用場景
1、后臺任務(如日志記錄、監控)
2、不需要與主線程同步的一次性任務
3、長時間運行的服務線程

注意事項
1、分離后無法再join該線程
2、分離線程不能返回結果給創建者線程
3、主線程退出可能導致分離線程被強制終止(取決于平臺和設置)
4、需要謹慎處理共享資源,因為缺乏同步機制

7、線程封裝

下面對創建線程步驟做一次封裝:

#pragma once#include <iostream>
#include <cstring>
#include <functional>
#include <pthread.h>namespace ThreadMoodule
{using work_t = std::function<void(std::string)>;static int NameId = 1;// 枚舉線程狀態enum STATUS{NEW,RUNNING,STOP};// 封裝線程class Thread{private:// 此處需要注意,寫為成員函數的時候,函數的第一個參數默認是this*,若下面線程函數就需要用static修改,或者寫在類外,由于封裝,因此加staticstatic void *Routine(void *args){Thread *td = static_cast<Thread *>(args);td->_task(td->_name);return 0;}void EnableDetach(){_joinable=false;}public:Thread(work_t task): _task(task), _status(STATUS::NEW), _joinable(true){_name = "thread_" + std::to_string(NameId++);}bool StartThread(){// 啟動線程if (_status != STATUS::RUNNING){// int n = pthread_create(&_tid, nullptr, Routine, nullptr);//第四個參數由于線程函數寫在類中,// 被static修飾無法使用this*指針,因此無法訪問成員變量,因此該參數傳入this,傳遞給函數。int n = pthread_create(&_tid, nullptr, Routine, this);if (n != 0){return false; // 創建失敗}_status = STATUS::RUNNING;return true;}return false;}bool StopThread() // 即取消{if (_status == STATUS::RUNNING){int n = pthread_cancel(_tid);if (n != 0){return false;}_status = STATUS::STOP;return true;}return false;}bool JoinThread(){if (_joinable){int n = pthread_join(_tid, nullptr);if (n != 0){return false;}_status = STATUS::STOP;return true;}return false;}void DetachThread(){//前提是沒有分離的EnableDetach();pthread_detach(_tid);}std::string Name(){return _name;}bool IsJoinAble(){return _joinable;}~Thread(){}private:std::string _name;pthread_t _tid;pid_t _pid;     // 多線程,所有pid都是一樣的bool _joinable; // 默認是不可分離的work_t _task;STATUS _status;};
}
#include "Thread.hpp"
#include <unistd.h>
#include <vector>#define THREAD_NUM 5int main()
{std::vector<ThreadMoodule::Thread> threads;for (int i = 0; i < THREAD_NUM; i++){ThreadMoodule::Thread t([](std::string name){while(true){std::cout<<name<<"執行任務……"<<std::endl;sleep(1);} });threads.emplace_back(t);}for(auto& n:threads){n.StartThread();}sleep(1);for(auto& e:threads){e.StopThread();}sleep(1);for(auto& e:threads){e.JoinThread();}return 0;
}

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

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

相關文章

「Java基本語法」標識符、關鍵字與常量

知識點解析 1&#xff0e;標識符&#xff08;Identifiers&#xff09;&#xff1a;用于命名類、方法、變量等。 標識符命名規則&#xff1a; 標識符由字母&#xff08;A-Z&#xff0c;a-z&#xff09;、數字&#xff08;0-9&#xff09;、下劃線“_”或美元符號“$”組成。標…

Nginx Stream 層連接數限流實戰ngx_stream_limit_conn_module

1.為什么需要連接數限流&#xff1f; 數據庫/Redis/MQ 連接耗資源&#xff1a;惡意腳本或誤配可能瞬間占滿連接池&#xff0c;拖垮后端。防御慢速攻擊&#xff1a;層疊式限速&#xff08;連接數&#xff0b;帶寬&#xff09;可阻擋「Slow Loris」之類的 TCP 低速洪水。公平接入…

LLMs之Structured Output:vLLM 結構化輸出指南—從約束生成到自動解析與高效實現

LLMs之Structured Output&#xff1a;vLLM 結構化輸出指南—從約束生成到自動解析與高效實現 導讀&#xff1a;隨著大語言模型&#xff08;LLM&#xff09;在各類任務中的廣泛應用&#xff0c;如何使其輸出具備可控性、結構化與可解析性&#xff0c;成為實際部署中的關鍵問題。…

32 C 語言字符處理函數詳解:isalnum、isalpha、iscntrl、isprint、isgraph、ispunct、isspace

1 isalnum() 函數 1.1 函數原型 #include <ctype.h>int isalnum(int c); 1.2 功能說明 isalnum() 函數用于檢查傳入的整數參數是否為 ASCII 編碼的字母或數字字符&#xff08;A - Z、a - z、0 - 9&#xff0c;對應 ASCII 值 65 - 90、97 - 122、48 - 57&#xff09;。…

在網絡排錯中,經常會用到的操作命令和其作用

在網絡排錯中&#xff0c;經常會用到的操作命令和其作用 網絡排錯是確保網絡連接正常運行的重要環節&#xff0c;通過使用一系列工具和命令&#xff0c;可以有效診斷和解決網絡問題。以下是常用的網絡排錯命令及其作用&#xff1a; 1.ping ping 是一個用于測試主機之間連通性…

C++中友元(friend)高級應用和使用示例

下面列出幾個 高級友元應用場景 與典型設計模式&#xff0c;并配以示例&#xff0c;幫助大家在實際項目中靈活運用 friend 機制。 1. ADL 友元注入&#xff08;“注入式友元”&#xff09; 場景&#xff1a;為某個類型定義非成員操作符&#xff08;如算術、流插入等&#xff0…

TCP相關問題 第一篇

TCP相關問題1 1.TCP主動斷開連接方為什么需要等待2MSL 如上圖所示:在被動鏈接方調用close&#xff0c;發送FIN時進入LAST_ACK狀態&#xff0c;但未收到主動連接方的ack確認&#xff0c;需要被動連接方重新發送一個FIN&#xff0c;而為什么是2MSL&#xff0c;一般認為丟失ack在…

STM32啟動文件學習(startup_stm32f40xx.s)

原代碼 ;******************** (C) COPYRIGHT 2016 STMicroelectronics ******************** ;* File Name : startup_stm32f40xx.s ;* Author : MCD Application Team ;* version : V1.8.0 ;* date : 09-November-2016 ;* Desc…

uni-app學習筆記二十三--交互反饋showToast用法

showToast部分文檔位于uniapp官網-->API-->界面&#xff1a;uni.showToast(OBJECT) | uni-app官網 uni.showToast(OBJECT) 用于顯示消息提示框 OBJECT參數說明 參數類型必填說明平臺差異說明titleString是提示的內容&#xff0c;長度與 icon 取值有關。iconString否圖…

【Ragflow】26.RagflowPlus(v0.4.0):完善解析邏輯/文檔撰寫模式全新升級

概述 在歷經半個月的間歇性開發后&#xff0c;RagflowPlus再次迎來一輪升級&#xff0c;正式發布v0.4.0。 開源地址&#xff1a;https://github.com/zstar1003/ragflow-plus 更新方法 下載倉庫最新代碼&#xff1a; git clone https://github.com/zstar1003/ragflow-plus.…

【論文解讀】Toolformer: 語言模型自學使用工具

1st author: ?Timo Schick? - ?Google Scholar? paper: Toolformer: Language Models Can Teach Themselves to Use Tools | OpenReview NeurIPS 2023 oral code: lucidrains/toolformer-pytorch: Implementation of Toolformer, Language Models That Can Use Tools, by…

Spring 官方推薦構造函數注入

1. 依賴關系明確 構造函數注入可以清晰地聲明類的依賴關系&#xff0c;所有必需的依賴項都通過構造函數參數傳遞&#xff0c;使得代碼的可讀性更高。這種方式讓類的使用者能夠直觀地了解類的依賴&#xff0c;而不需要通過注解或反射來猜測。 2. 增強代碼健壯性 構造函數注入…

[深度學習]搭建開發平臺及Tensor基礎

一、實驗目的 1. 掌握Windows下PyTorch 深度學習環境的配置 2. 掌握一種PyTorch開發工具 3. 理解張量并掌握Tensor的常用操作&#xff08;創建、調整形狀、加、減、乘、除、取絕對值、比較操作、數理統計操作 4. 掌握Tensor與Numpy的互相轉換操作 5. 掌握Tensor 的降維和…

【Zephyr 系列 14】使用 MCUboot 實現 BLE OTA 升級機制:構建安全可靠的固件分發系統

??關鍵詞:Zephyr、MCUboot、OTA 升級、BLE DFU、雙分區、Bootloader、安全固件管理 ??面向讀者:希望基于 Zephyr 為 BLE 設備加入安全 OTA 升級功能的開發者 ??預計字數:5200+ 字 ?? 前言:為什么你需要 OTA? 隨著設備部署數量增多與產品生命周期延長,遠程升級(…

App Search 和 Workplace Search 獨立產品現已棄用

作者&#xff1a;來自 Elastic The Search Product Team App Search 和 Workplace Search 的核心功能已集成到 Elasticsearch 和 Kibana 中。 我們宣布在 9.0 版本中棄用 App Search 和 Workplace Search。 如果你是 Elastic 的客戶&#xff0c;當前正在使用 App Search 和 Wo…

Spring Boot + OpenAI 構建基于RAG的智能問答系統

一、技術架構設計 1.1 系統架構圖 [前端]│▼ (HTTP/REST) [Spring Boot Controller]│▼ (Service Call) [問答處理服務層]├─? [知識庫檢索模塊] ──? [向量數據庫]└─? [OpenAI集成模塊] ──? [OpenAI API]│▼ [結果組裝與返回] 1.2 技術選型 組件技術棧版本要求…

Oracle實用參考(13)——Oracle for Linux物理DG環境搭建(2)

13.2. Oracle for Linux物理DG環境搭建 Oracle 數據庫的DataGuard技術方案,業界也稱為DG,其在數據庫高可用、容災及負載分離等方面,都有著非常廣泛的應用,對此,前面相關章節已做過較為詳盡的講解,此處不再贅述。 需要說明的是, DG方案又分為物理DG和邏輯DG,兩者的搭建…

【論文閱讀29】區間預測CIPM(2025)

這篇論文主要研究的是滑坡位移的區間預測方法&#xff0c;提出了一種新型的預測模型&#xff0c;叫做復合區間預測模型&#xff08;CIPM&#xff09;&#xff0c;并以三峽庫區的白家堡滑坡為案例進行了應用和驗證。論文的核心內容和貢獻包括&#xff1a; 背景與問題 滑坡位移預…

Linux 文件系統底層原理筆記:磁盤結構、ext2 文件系統與軟硬鏈接解析

文章目錄 一、理解硬件1.1 磁盤、服務器、機柜、機房1.2 磁盤物理結構1.3 磁盤的存儲結構1.4 磁盤的邏輯結構1.4.1 理解過程1.4.2 真實過程 1.5 CHS && LBA地址 二、引入文件系統2.1 引入"塊"概念2.2 引入"分區"概念2.3 引入"inode"概念…

75Qt窗口_Qt窗口概覽

Qt 窗? 是通過 QMainWindow類 來實現的。 QMainWindow 是?個為??提供主窗?程序的類&#xff0c;繼承? QWidget 類&#xff0c;并且提供了?個預定義的布局。 QMainWindow 包含 ?個菜單欄&#xff08;menu bar&#xff09;、多個?具欄(tool bars)、多個浮動窗?&#x…