多線程編程之Linux環境下的多線程(一)

1、Linux環境下的線程html

  相對於其餘操做系統,Linux系統內核只提供了輕量級進程的支持,並未實現線程模型。Linux是一種「多進程單線程」的操做系統,Linux自己只有進程的概念,而其所謂的「線程」本質上在內核裏仍然是進程linux

     進程是資源分配的單位,同一進程中的多個線程共享該進程的資源(如做爲共享內存的全局變量)。Linux中所謂的「線程」只是在被建立時clone了父進程的資源,所以clone出來的進程表現爲「線程」,這一點必定要弄清楚。所以,Linux「線程」這個概念只有在打引號的狀況下才是最準確的。api

  目前Linux中最流行的線程機制爲LinuxThreads,所採用的就是線程-進程「一對一」模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。LinuxThreads由Xavier Leroy負責開發完成,並已綁定在GLIBC中發行,它實現了一種BiCapitalized面向Linux的Posix 1003.1c 「pthread」標準接口。Linuxthread能夠支持Intel、Alpha、MIPS等平臺上的多處理器系統。 數組

  須要注意的是,Linuxthread線程模型存在一些缺陷,尤爲是在信號處理、調度和進程間同步原語方面都存在問題。而且,這個線程模型也不符合POSIX標準的要求。爲了解決LinuxThread的缺陷,RedHat開發了一套符合POSIX標準的新型線程模型:NPTL(Native POSIX Thread Library)。關於Linuxthread與NPTL的比較,請參考文章:Linux 線程模型的比較:LinuxThreads 和 NPTL緩存

2、Linux環境下的多線程編譯支持安全

  按照POSIX 1003.1c 標準編寫的程序與Linuxthread 庫相連接便可支持Linux平臺上的多線程,在程序中需包含頭文件pthread. h,在編譯連接時使用命令:多線程

gcc -D -REENTRANT -lpthread xxx. c

  其中-REENTRANT宏使得相關庫函數(如stdio.h、errno.h中函數) 是可重入的、線程安全的(thread-safe),-lpthread則意味着連接庫目錄下的libpthread.a或libpthread.so文件。  ide

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

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

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

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

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

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

3、Linux環境下的多線程函數

 3.1 線程建立

  在進程被建立時,系統會爲其建立一個主線程,而要在進程中建立新的線程,則能夠調用pthread_create函數:

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

  參數說明:

  • thread:指向pthread_create類型的指針,用於引用新建立的線程。
  • attr:用於設置線程的屬性,通常不須要特殊的屬性,因此能夠簡單地設置爲NULL。
  • start_routine:傳遞新線程所要執行的函數地址。
  • arg:新線程所要執行的函數的參數。

  返回值:

  調用若是成功,則返回值是0;若是失敗則返回錯誤代碼。

  每一個線程都有本身的線程ID,以便在進程內區分。線程ID在pthread_create調用時回返給建立線程的調用者;一個線程也能夠在建立後使用pthread_self()調用獲取本身的線程ID: 

pthread_self (void);

3.2 線程退出

  線程的退出方式有三種:

(1)執行完成後隱式退出;

(2)由線程自己顯示調用pthread_exit 函數退出;

pthread_exit (void * retval);

 

(3)被其餘線程用pthread_cance函數終止:

pthread_cancel (pthread_t thread);

 

  若是一個線程要等待另外一個線程的終止,可使用pthread_join函數,該函數的做用是調用pthread_join的線程將被掛起直到線程ID爲參數thread的線程終止:

pthread_join (pthread_t thread, void** threadreturn);

 

3.3 簡單的多線程示例

  一個簡單的Linux多線程示例以下:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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("Thread creation failed!");
        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 returned %s/n", (char *)thread_result);
    printf("Message is now %s/n", message);

    exit(EXIT_FAILURE);
}

void *thread_function(void *arg)
{
    printf("thread_function is running. Argument was %s/n", (char *)arg);
    sleep(3);
    strcpy(message, "Bye!");
    pthread_exit("Thank you for your CPU time!");
}
View Code

  編譯語句以下:

gcc -D_REENTRANT thread1.c -o thread1 –lpthread

  輸出結果是:

$./thread1[輸出]:
thread_function is running. Argument was Hello World
Waiting for thread to finish...
Thread joined, it returned Thank you for your CPU time!
Message is now Bye!

 

  在這個例子中,pthread_exit(void *retval)自己返回的就是指向某個對象的指針,所以,pthread_join(pthread_t th, void **thread_return);中的thread_return是二級指針,指向線程返回值的指針。能夠看到,咱們建立的新線程修改的數組message的值,而原先的線程也能夠訪問該數組。若是咱們調用的是fork而不是pthread_create,就不會有這樣的效果了。由於fork建立子進程以後,子進程會拷貝父進程,二者分離,相互不干擾,而線程之間則是共享進程的相關資源。

 

小結:

  本文主要講了Linux環境下的多線程基本概念,包括多線程的實現方式、函數接口、功能特性等。

相關文章
相關標籤/搜索