第十一章:線程


 

多進程在代碼中並不多見,因為它有很大的局限性,如分配大量資源、進程的PID個數有限等。現在更多地是使用多線程實現代碼並發。

一、線程的概念

線程是一種輕量級的代碼並發技術,對資源的要求較小。線程隸屬於某個進程,進程內部可以使用多線程,線程內部也可以使用多線程。

線程共享進程的資源,不需要太多額外的資源,每個線程只需要額外分配一個棧區即可。

在使用多線程之后,在程序設計時就可以把進程設計成在某一時刻不止做一件事,每個線程處理各自的任務。

 

我們之前寫的代碼都是單線程的,也就是main()函數線程,又稱主線程。主線程一旦結束,進程也就隨之結束,所有線程也隨之結束。

 

 

二、線程標識

和每個進程有一個進程PID一樣,每個線程也有一個線程ID。進程ID不可重復,但是線程ID屬於某個進程,只要在此進程中的線程ID不重復即可。

 

獲取自身線程ID使用函數為pthread_self(),其函數定義如下:

#include <pthread.h>

pthread_t pthread_self(void);

Compile and link with -pthread.

函數返回值類型是pthread_t,與進程的pid_t不同,pthread_t實現可能不是整數類型(操作系統不同,類型不同)。因此我們需要使用pthread_equal()函數來對兩個線程ID進行比較,其函數定義如下:

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

Compile and link with -pthread.

該函數若t1和t2線程ID相等,返回非0數值;否則,返回0。

 

Linux和Unix都在POSIX規范中對線程有了定義,主要是一個頭文件pthread.h + 一個共享庫libpthread.so。所有線程相關的函數、結構體和類型一般以pthread_開頭,比如接下來要講解的線程創建函數pthread_create()。

由於使用共享庫,在編譯鏈接時,需要加上-pthread或-lpthread

 

 

三、線程創建

新增的線程可以通過pthread_create()函數創建。其函數聲明如下:

#include <pthread.h>

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

 


函數參數以及返回值:

thread:返回的線程ID

attr:線程的屬性,一般給0即可

start_rountine:線程執行函數指針

arg:線程執行函數的函數參數

返回值:成功返回0;失敗返回錯誤碼,需要使用strerror()轉換為錯誤信息


 

示例代碼如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 #define PI 3.141592
 6 
 7 void *task(void *p)
 8 {
 9     int *r = (int *)p;
10     printf("半徑為%d圓面積為:%lf\n", *r, PI*(*r)*(*r));
11 }
12 
13 int main()
14 {
15     pthread_t id;
16 
17     int x = 10;
18     pthread_create(&id, 0, &task, &x);
19     sleep(1);    // 防止主線程先結束
20 
21     return 0;
22 }

 

 

四、線程終止

如果進程中任意線程調用了exit()、_Exit()或_exit(),那么整個進程就會終止。

因此,線程需要有自己的終止方式:

1. 線程的函數執行了return或exit()。

2. 調用線程終止函數pthread_exit(void *),返回值做為終止參數

3. 線程被其它線程終止

 

pthread_exit()函數聲明如下:

#include <pthread.h>

void pthread_exit(void *retval);

retval參數與傳給pthread_create()參數類似。進程中的其他線程可以通過pthread_join()函數訪問到這個指針,其函數聲明如下:

#include <pthread.h>

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

 


函數參數以及返回值:

thread:指定的線程ID

retval:pthread_exit()函數的返回值

返回值:成功返回0;失敗返回錯誤碼,需要使用strerror()轉換為錯誤信息


 

pthread_join()函數會導致調用線程一直阻塞,直到指定的線程調用pthread_exit()、從線程中返回或被取消。

對於取消線程,我們可以使用pthread_cancel()函數,其函數聲明如下:

#include <pthread.h>

int pthread_cancel(pthread_t thread);

 

示例代碼如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 void *task(void *p)
 6 {
 7     int *r = (int *)p;
 8     int area = 0;            // 只有int和void *可以互轉
 9     
10     area = (*r) * (*r);
11     
12     return (void *)area;    // 不能返回局部變量的地址
13 }
14 
15 int main()
16 {
17     pthread_t id;
18 
19     int x = 10;
20     pthread_create(&id, 0, &task, &x);
21     int area;
22     pthread_join(id, (void**)&area); // 取地址即可
23     printf("長度為%d正方形面積為:%d\n", x, area);
24 
25     return 0;
26 }

 

在默認情況下,線程的終止狀態會保存直到對該線程調用pthread_join()。如果線程已經被分離,線程的底層存儲資源可以在線程終止時被立即收回。在線程被分離后,就不能用pthread_join()函數等待它的終止狀態,因為對分離狀態的線程調用pthread_join()會產生定義行為。可以調用pthread_detach()分離線程。

#include <pthread.h>

int pthread_detach(pthread_t thread);

 

示例代碼如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 void *task(void *p)
 6 {
 7     int i;
 8     for(i = 0; i < 30; i++)
 9     {
10         printf("task:%d\n", i);
11         usleep(10000);
12     }
13 }
14 
15 int main()
16 {
17     pthread_t id;
18     pthread_create(&id, 0, task, 0);
19     pthread_detach(id);
20 //    pthread_join(id, 0);    // 沒有阻塞等待效果
21     int i;
22     for(i = 0; i < 30; i++)
23     {
24         printf("main:%d\n", i);
25         usleep(10000);
26     }
27 
28     return 0;
29 }

執行此代碼,可以發現主線程和task線程同時執行,而不是主線程阻塞等待task線程執行完成。

 

 

五、線程同步

使用線程並發並發就會導致一個問題:假設線程A對一個文件寫入3000個字符“a”,而另一個線程B對這個文件寫入3000個“b”,第三個線程C讀取這個文件,會導致讀取數據不一定是什么。

因為可能在一段時間內先執行了A;當A執行到一半CPU切換到執行B了,這時就會導致數據混亂。

 

為了保證數據一致性,我們需要保證線程同步。也就是讓線程在同一時間只允許一個線程訪問該變量。

 

一般采用的方式有原子操作、自旋鎖、信號量和互斥體。具體細節可查看:五、並發控制

線程對應的函數是以上函數的變體(一般是在以上函數前面加上pthread_,如自旋鎖初始化函數pthread_spin_lock())。

 

讀寫鎖:

讀寫鎖與互斥量類似,不過讀寫鎖允許更高的並行性。

其系列函數定義如下:

#include <pthread.h>

/* 初始化讀寫鎖 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
       const pthread_rwlockattr_t *restrict attr);

/* 銷毀讀寫鎖 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

/* 讀鎖 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

/* 寫鎖 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

/* 解鎖 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

 

條件變量:

條件變量使我們睡眠等待某種條件出現,然后線程才能繼續執行,與信號(signal)類似。它必須與互斥量配合使用。

其系列函數定義如下:

#include <pthread.h>

/* 初始化條件變量 */
int pthread_cond_init(pthread_cond_t *restrict cond,
       const pthread_condattr_t *restrict attr);

/* 注銷條件變量 */
int pthread_cond_destroy(pthread_cond_t *cond);

/* 喚醒條件變量 */
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

/* 等待條件變量 */
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

 

自旋鎖:

其系列函數定義如下:

#include <pthread.h>

/* 自旋鎖初始化 */
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

/* 自旋鎖注銷 */
int pthread_spin_destroy(pthread_spinlock_t *lock);

/* 上鎖/解鎖 */
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

 

互斥量:

其系列函數定義如下:

#include <pthread.h>

/* 初始化互斥量 */
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

/* 銷毀互斥量 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);

/* 上鎖/解鎖 */
int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock( pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

互斥量的示例代碼如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 char *name[5];
 6 int index;
 7 
 8 pthread_mutex_t lock;                // 1 聲明
 9 
10 void *task(void *p)
11 {
12     pthread_mutex_lock(&lock);        // 3 上鎖
13     name[index] = (char*)p;
14     usleep(10000);
15     index++;
16     pthread_mutex_unlock(&lock);    // 5 解鎖
17 }
18 
19 int main()
20 {
21     name[index] = "nihao";
22     index++;
23     pthread_mutex_init(&lock, 0);    // 2 初始化
24     pthread_t id1, id2;
25     pthread_create(&id1, 0, task, "hello");
26     pthread_create(&id2, 0, task, "world"); // 兩個進程運行,id1剛執行,id2也開始執行,暫停,id2覆蓋id1
27     pthread_join(id1, 0);
28     pthread_join(id2, 0);
29     pthread_mutex_destroy(&lock);    // 6 銷毀
30     int i;
31     for(i = 0; i < index; i++)
32         printf("%s\n", name[i]); // 空指針
33 
34     return 0;
35 }

 

 

下一章  第十四章:高級I/O

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM