目錄
執行緒的概念與實作方式
線程的實作方式
线程库中的其他方法
线程属性
线程属性初始化
线程分离
首頁 運維 linux運維 Linux線程的創建方式是什麼

Linux線程的創建方式是什麼

May 22, 2023 pm 06:38 PM
linux

    執行緒的概念與實作方式

    執行緒是進程內部的一條執行序列或執行路徑,一個行程可以包含多條執行緒。

    • 從資源分配的角度來看,行程是作業系統進行資源分配的基本單位。

    • 從資源調度的角度來看,執行緒是資源調度的最小單位,是程式執行的最小單位

    執行序列就是一組有序指令的集合——函數。

    線程是進程內部的一條執行序列,一個進程至少有一條線程,稱為主線程(main方法代表的執行序列),可以透過執行緒庫建立其他線程(給線程製定一個它要執行的函數),將創建的線程稱為函數線程。

    Linux線程的創建方式是什麼

    線程的實作方式

    • 核心級線程(由核心直接建立和管理線程,雖然創建開銷較大,但是可以利用多處理器的資源)

    • 用戶級線程(由線程庫創建和管理多個線程,線程的實作都是在用戶態,核心無法感知,創建開銷較小,無法使用多處理器的資源)

    • 混合級執行緒(結合以上兩種方式實現,可以利用多處理器的資源,從而在用戶空間中創建更多的線程,從而映射到內核空間的線程中,多對多,N:M(N>>M))

    Linux線程的創建方式是什麼

    ##Linux系統實現多執行緒的方式

    Linux 實作執行緒的機制非常獨特。

    從核心的角度來說,它並沒有線程這個概念。

    Linux 把所有的執行緒都當作行程來實作。內核並沒有為表徵線程準備特別的調度演算法或定義特別的資料結構。

    相反,執行緒僅被視為一個與其他行程共享某些資源的行程。

    每個執行緒都擁有唯一隸屬於自己的task_struct,所以在核心中,它看起來就像是一個普通的行程(只是執行緒和其他一些行程共享某些資源,如位址空間)

    執行緒與行程的差異

    • 行程是資源分配最小單位,執行緒是程式執行的最小單位;

    • 執行緒間的切換效率比起進程間的切換要高

    • 進程有自己獨立的位址空間,每啟動一個進程,系統就會為其分配位址空間,建立資料表來維護程式碼段、堆疊段和資料段,執行緒沒有獨立的位址空間,它使用相同的位址空間共享資料;

    • 建立一個執行緒比進程開銷小;

    • 執行緒佔用的資源要⽐進程少很多。

    • 線程之間通訊更方便,同一個進程下,線程共享全域變量,靜態變量等數據,進程之間的通信需要以通信的方式(IPC)進行;(但多執行緒程式處理好同步與互斥是個難點)

    • 多行程程式更安全,生命力更強,一個行程死掉不會對另一個行程造成影響(源自於有獨立的位址空間),多執行緒程式更不容易維護,一個執行緒死掉,整個行程就死掉了(因為共享位址空間);

    • 程式對資源保護要求高,開銷大,效率相對較低,執行緒資源保護要求不高,但開銷小,效率高,可頻繁切換;

    多執行緒開發的三個基本概念

    • 執行緒【建立、退出、等待】

    • 互斥鎖定【建立、銷毀、加鎖】、解鎖】

    • #條件【建立、銷毀、觸發、廣播、等待】

    線程庫的使用

    1.建立執行緒

    #include<phread.h>
    
    int pthread_create(pthread_t *id , pthread_attr_t *attr, void(*fun)(void*), void *arg);
    登入後複製

    • id :傳遞一個pthread_t類型的變數的位址,在建立成功後,用來取得新建立的執行緒的TID

    • #attr:指定執行緒的屬性預設使用NULL

    • #fun:執行緒函數的位址

    • arg:傳遞給執行緒函數的參數

    • #傳回值,成功回傳0,失敗回傳錯誤碼

    多執行緒程式碼範例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    //声明一个线程函数
    void *fun(void *);
    
    int main()
    {
    	printf("main start\n");
    
    	pthread_t id;
    	//创建函数线程,并且指定函数线程要执行的函数
    	int res = pthread_create(&id,NULL,fun,NULL);
    	assert(res == 0);
    
    	//之后并发运行
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    
    	printf("main over\n");
    	exit(0);
    }
    
    //定义线程函数
    void* fun(void *arg)
    {
    	printf("fun start\n");
    
    	int i = 0;
    	for(; i < 3;i++)
    	{
    		printf("fun running\n");
    		sleep(1);
    	}
    
    	printf("fun over\n");
    }
    登入後複製

    gcc編譯程式碼時報`undifined reference to xxxxx錯誤,都是因為程式中呼叫了一些方法,但沒有連接該方法所在的文件,例如下面的情況:

    Linux線程的創建方式是什麼

    連接庫文件編譯成功並執行,這一點在幫助手冊中也有提示:

    Compile and link with -pthread

    Linux線程的創建方式是什麼

    比较两次运行的结果发现前三条执行语句时一样的

    Linux線程的創建方式是什麼

    结论

    • 创建线程并执行线程函数,和调用函数是完全不同的概念。

    • 主线程和函数线程是并发执行的。

    • 线程提前于主线程结束时,不会影响主线程的运行

    • 主线程提前于线程结束时,整个进程都会结束,其他线程也会结束

    • 创建函数线程后,哪个线程先被执行是有操作系统的调度算法和机器环境决定。

    Linux線程的創建方式是什麼

    函数线程在主线程结束后也随之退出,原因:主线程结束时使用的是exit方法,这个方法结束的是进程。

    然而修改代码为:pthread_exit(NULL);此时主线程结束,函数线程会继续执行直至完成。即便如此,我们还是不推荐大家手动结束主线程,我们更喜欢让主线程等待一会。

    给线程函数传参

    ①值传递

    将变量的值直接转成void*类型进行传递

    因为线程函数接受的是一个void*类型的指针,只要是指针,32位系统上都是4个字节,值传递就只能传递小于或等于4字节的值。

    代码示例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    void *fun(void *);
    
    int main()
    {
    	printf("main start\n");
    
    	int a = 10;
    	
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,(void*)a);
    	assert(res == 0);
    
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    
    	printf("main over\n");
    	exit(0);
    }
    
    
    void* fun(void *arg)
    {
    	int b = (int)arg;
    	printf("b == %d\n",b);
    }
    登入後複製

    Linux線程的創建方式是什麼

    ②地址传递

    将变量(所有类型)的地址强转成void*类型进行传递,就和在普通函数调用传递变量的地址相似。

    主线程和函数线程通过这个地址就可以共享地址所指向的空间。

    一个进程内的所有线程是共享这个进程的地址空间。

    多线程下进程的4G虚拟地址空间

    Linux線程的創建方式是什麼

    一个进程内的所有线程对于全局数据,静态数据,堆区空间都是共享的。

    线程之间传递数据很简单,但是随之带来的问题就是线程并发运行时无法保证线程安全。

    代码示例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    int gdata = 10; //.data
    
    void *fun(void *);
    
    int main()
    {
    	int *ptr = (int *)malloc(4);//.heap
        *ptr = 10;
    	
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,(void*)ptr);
    	assert(res == 0);
    
        sleep(2);//等待两秒,保证函数线程已经讲数据修改
    
    	printf("main : gdata == %d\n",gdata);
        printf("main : *ptr = %d\n",*ptr);
    
    	exit(0);
    }
    
    
    void *fun(void *arg)
    {
    	int *p = (int*)arg;
    
        gdata = 20000;
        *p = 20;
    
    	printf("fun over\n");
    }
    登入後複製

    Linux線程的創建方式是什麼

    线程库中的其他方法

    线程退出的三种方式:

    • 线程从执行函数返回,返回值是线程的退出码;

    • 线程被同一进程的其他线程取消;

    • 调用pthread_exit()函数退出;

    等待线程终止

    int pthread_join(pthread_t thread, void **retval);
    args:
        pthread_t thread: 被连接线程的线程号,该线程必须位于当前进程中,而且不得是分离线程
        void **retval :该参数不为NULL时,指向某个位置 在该函数返回时,将该位置设置为已终止线程的退出状态
        return:
        线程连接的状态,0是成功,非0是失败
    登入後複製

    当A线程调用线程B并 pthread_join() 时,A线程会处于阻塞状态,直到B线程结束后,A线程才会继续执行下去。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。

    这里有三点需要注意:

    • 系统仅释放系统空间,你需要手动清除程序分配的空间,例如由 malloc() 分配的空间。

    • 2.一个线程只能被一个线程所连接。

    • 3.被连接的线程必须是非分离的,否则连接会出错。所以可以看出pthread_join()有两种作用:1-用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。2-对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。

    等待指定的子线程结束

    • 等待thread()指定的线程退出,线程未退出时,该方法阻塞

    • result接收thread线程退出时,指定退出信息

    int pthread_join(pthread_t id,void **result)//调用这个方法的线程会阻塞,直到等待线程结束
    登入後複製

    代码演示:

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    int main()
    {
    	printf("main start\n");
    
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,NULL);
    	assert(res == 0);
    
    	//之后并发运行
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    	
    	char *s = NULL;
    	pthread_join(id,(void **)&s);
    	printf("join : s = %s\n",s);
    	
    	exit(0);
    }
    
    //定义线程函数
    void* fun(void *arg)
    {
    	printf("fun start\n");
    
    	int i = 0;
    	for(; i < 10;i++)
    	{
    		printf("fun running\n");
    		sleep(1);
    	}
    
    	printf("fun over\n");
    
    	pthread_exit("fun over");//将该字符常量返回给主线程
    }
    登入後複製

    此时,主线程完成五次输出,就会等待子线程结束,阻塞等待,子线程结束后,最后,主线程打印join:s = fun over

    关于exit和join的一些详细说明:

    • 线程自己运行结束,或者调用pthread_exit结束,线程都会释放自己独有的空间资源;

    • 若线程是非分离的,线程会保留线程ID号,直到其他线程通过joining这个线程确认其已经死亡,join的结果是joining线程得到已终止线程的退出状态,已终止线程将消失;

    • 若线程是分离的,不需要使用pthread_exit(),线程自己运行结束,线程结束就会自己释放所有空间资源(包括线程ID号);

    • 子线程最终一定要使用pthread_join()或者设置为分离线程来结束线程,否则线程的资源不会被完全释放(使用取消线程功能也不能完全释放);

    • 主线程运行pthrea_exit(),会结束主线程,但是不会结束子线程;

    • 主线程结束,则整个程序结束,所以主线程最好使用pthread_join函数等待子线程结束,使用该函数一个线程可以等待多个线程结束;

    • 使用pthread_join函数的线程将会阻塞,直到被join的函数线程结束,该函数返回,但是它对被等待终止的线程运行没有影响;

    • 如果子线程使用exit()则可以结束整个进程;

    线程属性

    线程具有的属性可以在线程创建的时候指定;

    ——pthread_create()函数的第二个参数(pthread_attr_t *attr)表示线程的属性,在以前的例子中将其值设为NULL,也就是采用默认属性,线程的多项属性都是可以修改的,这些属性包括绑定属性,分离属性,堆栈属性,堆栈大小,优先级。

    系统默认的是非绑定,非分离,缺省1M的堆栈以及父子进程优先级相同

    线程结构如下:

    typedef struct
    {
        int             detachstate;     //线程的分离状态
        int             schedpolicy;    //线程调度策略
        struct sched_param  schedparam; //线程的调度参数
        int             inheritsched;   //线程的继承性
        int             scope;      //线程的作用域
        size_t          guardsize;  //线程栈末尾的警戒缓冲区大小
        int             stackaddr_set; //线程的栈设置
        void*           stackaddr;  //线程栈的位置
        size_t          stacksize;  //线程栈的大小
    } pthread_attr_t;
    登入後複製

    每一个属性都有对应的一些函数,用于对其进行查看和修改,下面分别介绍:

    线程属性初始化

    初始化和去初始化分别对应于如下的两个函数:

    #include <pthread.h>
    
    ①int pthread_attr_init(pthread_attr_t *attr);
    ②it pthread_attr_destroy(pthread_attr_t *attr);
    登入後複製

    ①功能:

    • 初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程

    参数:

    • attr:线程属性结构体

    返回值:

    • 成功:0

    • 失败:-1

    ②功能:

    • 销毁线程属性所占用的资源函数

    参数:

    • attr:线程属性结构体

    返回值:

    • 成功:0

    • 失败:-1

    线程分离

    线程的分离状态决定一个线程以什么样的方式来终止自己,这个在之前我们也说过了。

    • 默认状态下,线程是非分离状态,意味着原有的线程会等待所创建的线程结束。只有在pthread_join()函数返回后,才能释放创建的线程占用的系统资源,也才能视作该线程终止。

    • 若线程运行结束且无其他线程阻塞等待,则该线程处于分离状态,此时系统资源将立即被释放。应该根据自己的需要,选择适当的分离状态。

    相关API如下:

    #include <pthread.h>
    
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    登入後複製

    功能:设置线程分离状态

    参数:

    • attr:已初始化的线程属性

    • detachstate: 分离状态

    PTHREAD_CREATE_DETACHED(分离线程)

    PTHREAD_CREATE_JOINABLE(非分离线程)

    返回值:

    • 成功:0

    • 失败:非0

    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
    登入後複製

    功能:获取线程分离状态

    参数:

    • attr:已初始化的线程属性detachstate: 分离状态

    PTHREAD_CREATE_DETACHED(分离线程)

    PTHREAD _CREATE_JOINABLE(非分离线程)

    返回值:

    • 成功:0

    • 失败:非0

    注意:

    當一個執行緒被設定為分離執行緒時,假設此時該執行緒的執行速度非常快,它很可能在pthread_create返回之前就終止; 終止之後將執行緒號和系統資源移交給其他執行緒使用,這樣調用create就得到了錯誤的線程號,因此必須採取一些同步措施,可以在被創建的線程裡調用pthread_cond_timedwait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回,設置一段等待時間,是在多執行緒程式設計裡常用的方法。要避免使用像wait()這樣的函數,因為它們會讓整個進程進入睡眠狀態,而無法解決執行緒同步問題。

    以上是Linux線程的創建方式是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    本網站聲明
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

    熱AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智慧驅動的應用程序,用於創建逼真的裸體照片

    AI Clothes Remover

    AI Clothes Remover

    用於從照片中去除衣服的線上人工智慧工具。

    Undress AI Tool

    Undress AI Tool

    免費脫衣圖片

    Clothoff.io

    Clothoff.io

    AI脫衣器

    Video Face Swap

    Video Face Swap

    使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

    熱工具

    記事本++7.3.1

    記事本++7.3.1

    好用且免費的程式碼編輯器

    SublimeText3漢化版

    SublimeText3漢化版

    中文版,非常好用

    禪工作室 13.0.1

    禪工作室 13.0.1

    強大的PHP整合開發環境

    Dreamweaver CS6

    Dreamweaver CS6

    視覺化網頁開發工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神級程式碼編輯軟體(SublimeText3)

    熱門話題

    Java教學
    1657
    14
    CakePHP 教程
    1415
    52
    Laravel 教程
    1309
    25
    PHP教程
    1257
    29
    C# 教程
    1230
    24
    Linux體系結構:揭示5個基本組件 Linux體系結構:揭示5個基本組件 Apr 20, 2025 am 12:04 AM

    Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

    vscode終端使用教程 vscode終端使用教程 Apr 15, 2025 pm 10:09 PM

    vscode 內置終端是一個開發工具,允許在編輯器內運行命令和腳本,以簡化開發流程。如何使用 vscode 終端:通過快捷鍵 (Ctrl/Cmd ) 打開終端。輸入命令或運行腳本。使用熱鍵 (如 Ctrl L 清除終端)。更改工作目錄 (如 cd 命令)。高級功能包括調試模式、代碼片段自動補全和交互式命令歷史。

    git怎麼查看倉庫地址 git怎麼查看倉庫地址 Apr 17, 2025 pm 01:54 PM

    要查看 Git 倉庫地址,請執行以下步驟:1. 打開命令行並導航到倉庫目錄;2. 運行 "git remote -v" 命令;3. 查看輸出中的倉庫名稱及其相應的地址。

    vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

    在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

    vscode終端命令不能用 vscode終端命令不能用 Apr 15, 2025 pm 10:03 PM

    VS Code 終端命令無法使用的原因及解決辦法:未安裝必要的工具(Windows:WSL;macOS:Xcode 命令行工具)路徑配置錯誤(添加可執行文件到 PATH 環境變量中)權限問題(以管理員身份運行 VS Code)防火牆或代理限制(檢查設置,解除限制)終端設置不正確(啟用使用外部終端)VS Code 安裝損壞(重新安裝或更新)終端配置不兼容(嘗試不同的終端類型或命令)特定環境變量缺失(設置必要的環境變量)

    notepad怎麼運行java代碼 notepad怎麼運行java代碼 Apr 16, 2025 pm 07:39 PM

    雖然 Notepad 無法直接運行 Java 代碼,但可以通過借助其他工具實現:使用命令行編譯器 (javac) 編譯代碼,生成字節碼文件 (filename.class)。使用 Java 解釋器 (java) 解釋字節碼,執行代碼並輸出結果。

    sublime寫好代碼後如何運行 sublime寫好代碼後如何運行 Apr 16, 2025 am 08:51 AM

    在 Sublime 中運行代碼的方法有六種:通過熱鍵、菜單、構建系統、命令行、設置默認構建系統和自定義構建命令,並可通過右鍵單擊項目/文件運行單個文件/項目,構建系統可用性取決於 Sublime Text 的安裝情況。

    Linux的主要目的是什麼? Linux的主要目的是什麼? Apr 16, 2025 am 12:19 AM

    Linux的主要用途包括:1.服務器操作系統,2.嵌入式系統,3.桌面操作系統,4.開發和測試環境。 Linux在這些領域表現出色,提供了穩定性、安全性和高效的開發工具。

    See all articles