Linux學習5-線程

線程

1.1什麼是線程?

  在一個程序中的多個執行路線就叫作線程(thread)。更準確的定義是:線程是一個進程內部的一個控制序列。緩存

    要搞清楚fork系統調用和建立新線程之間的區別。當進程執行fork調用時,將建立出該進程的一份新的副本。這個新進程擁有本身的變量和本身的PID,它的時間調度也是獨立的,它的執行(一般)安全

幾乎徹底獨立於父進程。當在進程中建立一個新線程時,新的執行線程將擁有本身的棧(所以也擁有本身的局部變量),但與它的建立者共享全局變量、文件描述符、信號處理函數和當前目錄狀態。  多線程

1.2第一個線程程序

  線程有一套完整的相關的函數庫調用,它們絕大多數以pthread_開頭。爲了使用這些函數調用,咱們必須定義宏_REENTRANT,在程序中包含頭文件pthread.h,而且在編譯程序時須要使用選項-lpthread來連接線程庫。函數

在一個多線程程序裏,默認狀況下,只有一個errno變量供全部的線程共享。在一個線程準備獲取剛纔的錯誤代碼時,該變量很容易被另外一個線程中的函數調用所改變。相似的問題還存在於fputs之類的函數中,這些函數一般用一個單獨的全局性區域來緩存輸出數據。spa

爲解決這個問題,須要使用可重入的例程。可重入代碼能夠被屢次調用而仍然工做正常。編寫的多線程程序,經過定義宏_REENTRANT來告訴編譯器咱們須要可重入功能,這個宏的定義必須出現於程序中的任何#include語句以前。線程

_REENTRANT爲咱們作三件事情,而且作的很是優雅:3d

(1)它會對部分函數從新定義它們的可安全重入的版本,這些函數名字通常不會發生改變,只是會在函數名後面添加_r字符串,如函數名gethostbyname變成gethostbyname_r。指針

(2)stdio.h中原來以宏的形式實現的一些函數將變成可安全重入函數。code

(3)在error.h中定義的變量error如今將成爲一個函數調用,它可以以一種安全的多線程方式來獲取真正的errno的值。對象

舉個例子便於更好理解可重入的重要性:

 

int g_val = 1;//定義一個全局變量

void add()
{
    g_val++;
}

 

 若是有n個線程調用該函數,g_val是一個全局變量,若是加上-D _REENTRANT,則可重入,即g_val不會受其餘線程的影響;可是沒加-D _REENTRANT的時候,n-1個線程調用該函數時變量g_val就會彼此影響,出現不可預料的結果。

不會影響的緣由是,加-D _REENTRANT後雖然調用的仍是這個函數,可是不一樣的是該機制自動把上面的函數變成了

void add_r()
{
    g_val++;
}

 

調用的函數不一樣了。

 

建立新線程的函數:

#include <pthread.h>

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

第一個參數thread指向pthred_t類型數據的指針。線程被建立時,這個指針指向的變量中將被寫入一個標識符,咱們用該標識符來引用新線程。

第二個參數用於設置線程的屬性。通常不須要特殊的屬性,因此能夠設爲NULL。

第三個參數是一個函數地址,該函數以一個指向void的指針爲參數,返回的也是一個指向void的指針。

用fork調用後,父子進程將在同一個位置繼續執行下去,只是fork調用的返回值上不一樣的;

但對於新線程來講,咱們必須明確地提供給它一個函數指針,新線程將在這個新位置開始執行。

第四個參數是要傳遞給上面函數(第三個參數)的參數。

該函數調用成功時返回值是0,若是失敗則返回錯誤代碼。

 

終止線程的函數:

#include <pthread.h>

void ptherad_exit(void *retval);

線程經過調用pthread_exit函數終止執行,就如同進程在結束時調用exit函數同樣。該函數的做用是,終止調用它的線程並返回一個指向某個對象的指針。

注意,絕對不能用它來返回一個指向局部變量的指針,由於線程在調用該函數後,這個局部變量就再也不存在了,這將引發嚴重的程序漏洞。

 

等待線程的函數:

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

pthread_join函數在線程中的做用等價於進程中用來收集子進程信息的wait函數。

第一個參數指定了要等待的線程,線程經過pthread_create返回的標識符來指定。

第二個參數是一個指針,它指向了另外一個指針,然後者指向了返回值。

這個函數成功返回0失敗返回錯誤代碼。

 

第一個線程示例代碼:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *thread_function(void *arg);
char message[]="hello world!";

int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;
   
    res = pthread_create(&a_thread,NULL,thread_function,(void *)&message);
    if(res != 0)
    {
        perror("create  thread is failed\n ");
        exit(EXIT_FAILURE);     
    }
    
    printf("Waiting for thread to finish...\n");
    res = pthread_join(a_thread,&thread_result);
    if (res!=0)
    {
       perror("thread join failed!\n");
       exit(EXIT_FAILURE);
      
    }  

    printf("Thread joined,it return %s\n",(char *)thread_result);
    printf("message now is %s\n",message);
    exit(EXIT_SUCCESS);   
 
}

void *thread_function(void *arg)
{
    printf("th_func is running,Aragement was %s\n",(char *)arg);
    sleep(5);
    strcpy(message,"bye");
    pthread_exit("Thanks for the CPU time\n"); 


}

 

執行結果:

 

 編譯這個程序的時候,咱們首先須要定義宏_REENTRANT。在少數系統上,可能還須要定義宏_POSIX_C_SOURCE。(然而我並無加也能實現功能,待解決)

 接下來必須連接正確的線程庫。 

 我係統默認的線程庫是NPTL(查看頭文件/usr/include/pthread.h 若是顯示版權日期在2003年或更晚,那基本你的Linux發行版使用的是NPTL實現)

 

 

 使用以下命令編譯和連接:

cc -D_REENTRANT thread1.c -o thread1 -lpthread

ps:我把cc 換成 gcc 編譯經過。

 

 分析:main函數在建立新線程成功後,繼續執行。而後執行pthread_join函數,一直等到它指定的線程終止才返回。而後主線程打印新線程返回值和全局變量後結束。

 而新建立的線程將會執行thread_function。先打印本身的參數,睡眠5S後,更改全局變量最後退出時向主線程返回一個字符串。

相關文章
相關標籤/搜索