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

來源:互聯網
上載者:User

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

我們知道,進程在各自獨立的地址空間中運行,進程之間共用資料需要用處理序間通訊機制,有些情況需要在一個進程中同時執行多個控制流程程,這時候線程就派上了用場,比如實現一個圖形介面的下載軟體,一方面需要和使用者互動,等待和處理使用者的滑鼠鍵盤事件,另一方面又需要同時下載多個檔案,等待和處理從多個網路主機發來的資料,這些任務都需要一個“等待-處理”的迴圈,可以用多線程實現,一個線程專門負責與使用者互動,另外幾個線程每個線程負責和一個網路主機通訊。

註: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));      }  }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.