歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux多線程實踐(二)線程基本API(POSIX)

Linux多線程實踐(二)線程基本API(POSIX)

日期:2017/3/1 12:19:22   编辑:關於Linux

我們知道,進程在各自獨立的地址空間中運行,進程之間共享數據需要用進程間通信機制,有些情況需要在一個進程中同時執行多個控制流程,這時候線程就派上了用場,比如實現一個圖形界面的下載軟件,一方面需要和用戶交互,等待和處理用戶的鼠標鍵盤事件,另一方面又需要同時下載多個文件,等待和處理從多個網絡主機發來的數據,這些任務都需要一個“等待-處理”的循環,可以用多線程實現,一個線程專門負責與用戶交互,另外幾個線程每個線程負責和一個網絡主機通信。

注:linux 2.6 以後的線程就是由用戶態的pthread庫實現的.使用pthread以後, 在用戶看來, 每一個task_struct就對應一個線程, 而一組線程以及它們所共同引用的一組資源就是一個進程.在linux 2.6中, 內核有了線程組的概念, task_struct結構中增加了一個tgid(thread group id)字段. getpid(獲取進程ID)系統調用返回的也是tast_struct中的tgid, 而tast_struct中的pid則由gettid系統調用來返回。 當線程停止/繼續, 或者是收到一個致命信號時, 內核會將處理動作施加到整個線程組中。

比如程序a.out運行時,創建了一個線程。假設主線程的pid是10001、子線程是10002(它們的tgid都是10001)。這時如果你kill 10002,是可以把10001和10002這兩個線程一起殺死的,盡管執行ps命令的時候根本看不到10002這個進程。如果你不知道linux線程背後的故事,肯定會覺得非常奇怪。
與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”開頭,要使用這些函數庫,要通過引入頭文,而且鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項[Ubuntu系列系統需要添加的是”-pthread”選項而不是”-lpthread”,如Ubuntu 14.04版本,深度Ubuntu等] 下面開始介紹posix線程基本的API: pthread_create
int pthread_create(pthread_t *restrict thread,  
        const pthread_attr_t *restrict attr,  
        void *(*start_routine)(void*), void *restrict arg);  

創建一個新的線程

參數

thread:線程ID

attr:設置線程的屬性,一般設置為NULL表示使用默認屬性

start_routine:是個函數地址,線程啟動後要執行的函數

arg:傳給線程啟動函數的參數

返回值:成功返回0;失敗返回錯誤碼;

以前學過的系統函數都是成功返回0,失敗返回-1,而錯誤號保存在全局變量errno中,而pthread庫的函數都是通過返回值返回錯誤號,雖然每個線程也都有一個errno,但這是為了兼容其它函數接口而提供的,pthread庫本身並不使用它,通過返回值返回錯誤碼更加清晰。由於pthread_create的錯誤碼不保存在errno中,因此不能直接用perror(3)打印錯誤信息,可以先用strerror(3)把錯誤號轉換成錯誤信息再打印。讀取返回值要比讀取線程內的errno變量的開銷更小!
/** 實踐: 新的錯誤檢查與錯誤退出函數 **/  
inline void err_check(const std::string &msg, int retno)  
{  
    if (retno != 0)  
        err_exit(msg, retno);  
}  
inline void err_exit(const std::string &msg, int retno)  
{  
    std::cerr << msg << ": " << strerror(retno) << endl;  
    exit(EXIT_FAILURE);  
}  
pthread_exit
void pthread_exit(void *value_ptr); 

value_ptr:value_ptr不要指向一個局部變量,因為當其它線程得到這個返回指針時線程函數已經退出了。
返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)

如果需要只終止某個線程而不終止整個進程,可以有三種方法:

1、從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit,而如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止。

2、一個線程可以調用pthread_cancel 終止同一進程中的另一個線程。

3、線程可以調用pthread_exit終止自己。

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

當pthread_create 中的 start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的返回值,類似於父進程調用wait(2)得到子進程的退出狀態。

調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:

1、如果thread線程通過return返回,value_ptr所指向的單元裡存放的是thread線程函數的返回值。

2、如果thread線程被別的線程調用pthread_cancel異常終止掉,value_ptr所指向的單元裡存放的是常數PTHREAD_CANCELED。

3、如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。

如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。

/** 示例: 等待線程退出 **/  
void *thread_rotine(void *args)  
{  
    for (int i = 0; i < 10; ++i)  
    {  
        printf("B");  
        fflush(stdout);  
        usleep(20);  
    }  
    pthread_exit(NULL);  
}  
  
int main()  
{  
    pthread_t thread;  
    int ret = pthread_create(&thread, NULL, thread_rotine, NULL);  
    err_check("pthread_create", ret);  
  
  
    for (int i = 0; i < 10; ++i)  
    {  
        printf("A");  
        fflush(stdout);  
        usleep(20);  
    }  
  
    ret = pthread_join(thread, NULL);  
    err_check("pthread_join", ret);  
    putchar('\n');  
    return 0;  
}  
pthread_self
pthread_t pthread_self(void);  
返回線程ID

在Linux上,pthread_t類型是一個地址值,屬於同一進程的多個線程調用getpid(2)可以得到相同的進程號,而調用pthread_self(3)得到的線程號各不相同。線程id只在當前進程中保證是唯一的,在不同的系統中pthread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印。

/** 示例:主控線程與子線程傳遞數據 **/  
typedef struct _Student  
{  
    char name[20];  
    unsigned int age;  
} Student;  
  
void *threadFunction(void *args)  
{  
    cout << "In Thread: " << pthread_self() << endl;  
    Student tmp = *(Student *)(args);  
    cout << "Name: " << tmp.name << endl;  
    cout << "Age: " << tmp.age << endl;  
  
    pthread_exit(NULL);  
}  
  
int main()  
{  
    Student student = {"tach",22};  
  
    pthread_t thread;  
    //啟動創建並啟動線程  
    pthread_create(&thread,NULL,threadFunction,&student);  
    //等待線程結束  
    pthread_join(thread,NULL);  
  
    return 0;  
}  
pthread_cancel
int pthread_cancel(pthread_t thread);
線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態決定。

pthread_detach

int pthread_detach(pthread_t thread);  

一般情況下,線程終止後,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止(僵線程)。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。對一個尚未detach的線程調用pthread_join或pthread_detach都可以把該線程置為detach狀態,也就是說,不能對同一線程調用兩次pthread_join,或者如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

這個函數既可以在主線程中調用,也可以在thread_function裡面調用。

總結:進程 VS. 線程

進程(pid_t)

線程(pthread_t)

Fork

Pthread_create

Waitpit

Pthread_join/Pthread_detach

Kill

Pthread_cancel

Pid

Pthead_self

Exit/return

Pthread_exit/return

僵屍進程(沒有調用wait/waitpid等函數)

僵屍線程(沒有調用pthread_join/pthread_detach)


/** 將並發echo server改造成多線程形式  **/
void echo_server(int clientSocket);  
void *thread_routine(void *arg);  
int main()  
{  
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  
    if (sockfd == -1)  
        err_exit("socket error");  
  
    int optval = 1;  
    if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1)  
        err_exit("setsockopt error");  
  
    struct sockaddr_in serverAddr;  
    serverAddr.sin_family = AF_INET;  
    serverAddr.sin_port = htons(8002);  
    serverAddr.sin_addr.s_addr = INADDR_ANY;    //綁定本機的任意一個IP地址  
    if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)  
        err_exit("bind error");  
  
    if (listen(sockfd,SOMAXCONN) == -1)  
        err_exit("listen error");  
  
    while (true)  
    {  
        int peerSockfd = accept(sockfd, NULL, NULL);  
        if (peerSockfd == -1)  
            err_exit("accept error");  
  
        pthread_t tid;  
        /**注意: 下面這種用法可能會產生問題 
                當另一個連接快讀快速到達, peerSockfd的內容更改, 
                新創建的線程尚未將該值取走時,線程讀取的就不是 
                我們原來想讓線程讀取的值了 
        int ret = pthread_create(&tid, NULL, thread_routine, (void *)&peerSockfd); 
        **/  
        //解決方案: 為每一個鏈接創建一塊內存 ,注意之後要釋放 
        int *p = new int(peerSockfd);  
        int ret = pthread_create(&tid, NULL, thread_routine, p);  
        if (ret != 0)  
            err_thread("pthread_create error", ret);  
    }  
    close(sockfd);  
} 
void *thread_routine(void *args)  
{  
    //將線程設置分離狀態, 避免出現僵屍線程  
    pthread_detach(pthread_self());  
    int peerSockfd = *(int *)args;  
    //注意函數中指針取出之後記得將內存釋放掉 
    delete (int *)args;  
  
    echo_server(peerSockfd);  
    cout << "thread " << pthread_self() << " exiting ..." << endl;  
    pthread_exit(NULL);  
}  
void echo_server(int clientSocket)  
{  
    char buf[BUFSIZ] = {0};  
    int readBytes;  
    while ((readBytes = read(clientSocket, buf, sizeof(buf))) >= 0)  
    {  
        if (readBytes == 0)  
        {  
            cerr << "client connect closed" << endl;  
            break;  
        }  
        if (write(clientSocket, buf, readBytes) == -1)  
        {  
            cerr << "server thread write error" << endl;  
            break;  
        }  
        cout << buf;  
        bzero(buf, sizeof(buf));  
    }  
}
Copyright © Linux教程網 All Rights Reserved