1. 程式人生 > >linux下多線程編程

linux下多線程編程

更多 lee 互斥 進程和線程 除了 問題: 新手 open 函數回調

linux下多線程編程


本文將介紹linux系統下多線程編程中,線程同步的各種方法。包括:
互斥量(mutex)
讀寫鎖
條件變量
信號量
文件互斥
在介紹不同的線程同步的方法之前,先簡單的介紹一下進程和線程的概念, 它們的優缺點,線程相關的API,讀者——寫者問題和哲學家就餐問題。
#基礎知識
###1. 進程和線程
進程(process)是指在系統中正在運行的一個應用程序,是系統資源分配的基本單位,在內存 中有其完備的數據空間和代碼空間,擁有完整的虛擬空間地址。一個進程所擁有的數據和變量只 屬於它自己。
線程(thread)是進程內相對獨立的可執行單元,所以也被稱為輕量進程(lightweight processes );是操作系統進行任務調度的基本單元。它與父進程的其它線程共享該進程所擁有的全部代碼 空間和全局變量,但擁有獨立的堆棧(即局部變量對於線程來說是私有的)。
####1.1 線程和進程的區別:
線程是進程的一部分,所以線程有的時候被稱為是輕量級進程。
一個沒有線程的進程是可以被看作單線程的,如果一個進程內擁有多個線程, 進程的執行過程不是一條線(線程)的,而是多條線(線程)共同完成的。
系統在運行的時候會為每個進程分配不同的內存區域,但是不會為線程分配內存(線程所使 用的資源是它所屬的進程的資源),線程組只能共享資源。那就是說,除了CPU之外(線程在運 行的時候要占用CPU資源),計算機內部的軟硬件資源的分配與線程無關,線程只能共享它所屬 進程的資源。
與進程的控制表PCB相似,線程也有自己的控制表TCB,但是TCB中所保存的線程狀態比PCB表 中少很多
進程是系統所有資源分配時候的一個基本單位,擁有一個完整的虛擬空間地址,並不依賴線 程而獨立存在。
更加詳細的介紹,請參考這裏。
####1.2 線程的優點
由於以下原因,行業內廣泛地在編程庫和操作系統中實現線程:
減少內存占用量。創建另一個線程的內存消耗量被限制為線程所需要的堆棧加上線程管理器需 要的一些簿記內存。
不需要采用先進的技術來訪問服務器全局數據。如果數據有可能由另一個同時運行的線程修改, 則要做的一切就是使用互斥體 保護相關段。
創建線程所需要的時間大大小於創建進程所需要的時間,原因是不必復制堆部分(它可能很大)。
在線程之間進行環境切換時,內核在調度器中花的時間比在過程之間進行切換花的時間少。 這給負擔很重的服務器留下了更多的cpu時間處理工作。
####1.3 線程的缺點
盡管線程在現代計算機中極具重要性,它們卻有很多缺點:
編程錯誤代價慘重。如果一個線程崩潰,會使得整個服務器停止。一個壞線程可能會毀壞全局 數據,導致其他線程無法工作
容易產生編程錯誤。程序員必須不斷考慮其他一些線程可能正在做引起麻煩的事情,以及如何 避免這種麻煩。需要采用額外的防範方法編制程序。
線程服務器在同步漏洞方面聲名狼藉,這些漏洞幾乎無法在測試中進行復制,卻在生產期間很 不合時宜地出現。這類漏洞發生幾率之所以如此高,是由於使用共享地址空間,這會產生更高程 度的線程交互。
有時候互斥體爭用難以控制。如果在同一時間有太多的線程想得到相同的互斥體,就會導致過 度的環境切換,大量的CPU時間就會花在內核調度器上,因而幾乎沒有時間執行工作。
32位系統限制為每個線程使用4G地址空間。由於所有的線程都共享相同的地址空間,理論上整 個服務器就被限制為使用4G RAM,即便實際上有更多的物理RAM也不例外。實際使用時,地址空間 會在一個小得多的限值下開始變得非常擁擠。
擁擠的32位地址空間會帶來另一個問題。每個線程都需要一些堆棧空間,在分配了堆棧後, 即便不使用所分配的大部分空間,服務器也會為其保留地址空間。每個新堆棧都會減少用於堆的 潛在空間。因此,即使有足夠的物理內存,也不可能同時使用大型緩沖區,同時使用大量並發線 程,以及同時為每個線程提供足夠的堆棧空間。
####1.4 進程的優點
線程的缺點與使用多進程的優點相對應:
編程錯誤並不致命。盡管有可能發生,但一個壞分支服務器進程並不容易中斷整個服務器。
編程錯誤發生的可能性小得多。在大多數時候,程序員只需要考慮一個線程的執行,而不用受 可能的並發侵入者的打擾。
飄忽不定的漏洞少得多。如果漏洞出現一次,通常非常容易復制。由於各個分支進程有自己的 地址空間,它們之間並沒有太多的交互。
在32位系統中,地址空間用完的問題並不嚴重。
####1.5 進程的缺點
內存利用不夠好。當子進程發生分支時,會不必要地復制大型內存程序段。
需要采用特殊技術實現進程數據共享。(IPC)
創建進程比創建線程需要更多的內核系統開銷。對性能的一個很大的打擊是需要 復制父進程的 數據段。不過,Linux 在這方面的手段是執行所謂的copy-on-write 。除非子進程或父進程修改了 父進程頁,否則並不真正復制父進程頁。在此之前,父子進程使用相同的頁。
進程之間的環境切換比線程之間的環境切換更為耗時,因為內核需要切換頁,文件描述符表 及其他額外的內容信息。留給服務器執行實際工作的時間減少。
以上關於進程和線程的優缺點,都來自《深入理解mysql核心技術》
###2. 線程API
進程原語和線程原語的比較:
進程原語 線程原語 描述
fork pthread_create 創建新的控制流
exit pthread_exit 從現有的控制流退出
waitpid pthread_join 從控制流中得到退出狀態
atexit pthread_cancle_push 註冊在退出控制流時調用的函數
getpid pthread_self 獲取控制流的ID
abort pthread_cancle 請求控制流的非正常退出
###3. 讀者-寫者問題
讀者————寫者問題是一個用信號量實現的經典進程同步問題。在系統中,一個數據集(如文件或 記錄) 被幾個並發進程共享,這些線程分兩類,一部分只要求進行讀操作,稱之為“讀者”; 另一類要求寫或修改操作,我們稱之為“寫者“。一般而言,對一個數據集,為了保證數據的完整 性、正確性,允許多個讀者進程同時訪問,但是不允許一個寫者進程同其它任何一個進程(讀者 或者寫者)同時訪問,而這類問題就稱之為”讀者-寫者”問題。
讀者優先的算法在操作系統相關的書籍中都有介紹,這是一種最簡單的解決辦法: 當沒有寫進 程正在訪問共享數據集時,讀進程可以進入訪問,否則必須等待。而讀者優先的算法存在”餓死 寫者”線程的問題:只要有讀者不斷到來,寫者就要持久地等待,直到所有的讀者都讀完且沒有 新的讀者到來時寫者才能寫數據集。而在很多情況下我們需要避免”餓死寫者”,故而采用寫者優 先算法:
在寫者優先算法中,我們要實現的目標是:
1.要讓讀者與寫者之間、以及寫者與寫者之問要互斥地訪同數據集; 2.在無寫進程到來時各讀者可同時訪問數據集; 3.在讀者和寫者都等待時訪問時寫者優先.
在實現寫者優先時,增加一個互斥量,用於寫者優先。當有寫者來時,就不在允許讀者去讀取數據, 等待正在讀數據的讀者完成以後開始寫數據,以此實現寫者優先。
###4. 哲學家就餐問題
哲學家就餐問題可以這樣表述,假設有五位哲學家圍坐在一張圓形餐桌旁,做以下兩件事 情之一:吃飯,或者思考。吃東西的時候,他們就停止思考,思考的時候也停止吃東西。餐桌中 間有一大碗意大利面,每兩個哲學家之間有一只餐叉。因為用一只餐叉很難吃到意大利面,所以 假設哲學家必須用兩只餐叉吃東西。他們只能使用自己左右手邊的那兩只餐叉。哲學家就餐問題 有時也用米飯和筷子而不是意大利面和餐叉來描述,因為很明顯,吃米飯必須用兩根筷子。
#多線程編程示例
###5 互斥量(mutex)
互斥鎖創建
有兩種方法創建互斥鎖,靜態方式和動態方式。POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER 來靜態初始化互斥鎖,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;在LinuxThreads實現中, pthread_mutex_t是一個結構,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。
動態方式是采用pthread_mutex_init()函數來初始化互斥鎖,API定義如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
*mutexattr)
其中mutexattr用於指定互斥鎖屬性(見下),如果為NULL則使用缺省屬性。 pthread_mutex_destroy ()用於註銷一個互斥鎖,API定義如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
鎖操作
鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖 pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到, 而必須等待解鎖。對於普通鎖和適應鎖類型,解鎖者可以是同進程內任何線程; 而檢錯鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對於嵌套鎖,文檔和實現要求必須由 加鎖者解鎖,但實驗結果表明並沒有這種限制,這個不同目前還沒有得到解釋。在同一進程中 的線程,如果加鎖後沒有解鎖,則任何其他線程都無法再獲得鎖。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock() 語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回 EBUSY而不是掛起等待。
互斥量實現讀者寫者問題
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing

pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; // it‘s mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; //it‘s mean writer can writing

int readCount = 0;

void* reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);
pthread_mutex_lock(&rd);
readCount++;
if( readCount == 1)
{
pthread_mutex_lock(&wr);
}
pthread_mutex_unlock(&rd);

printf("reader %d is reading\n", id);
sleep( rand() % 3);

pthread_mutex_lock(&rd);
readCount--;
if (readCount == 0)
{
pthread_mutex_unlock(&wr);
}
pthread_mutex_unlock(&rd);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}

void* writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 3);
pthread_mutex_lock(&wr);
printf("\twriter is writing\n");
sleep( rand() % 3);
pthread_mutex_unlock(&wr);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");

}


int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writerTid;
int i;


err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can‘t create process for writer");
}

for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
if (err != 0)
{
err_quit("can‘t create process for reader");
}
}
pause();
return 0;
}
對於使用了多線程的程序,編譯方法如下:
g++ reader_writer.c -l pthread -o a.out
互斥量實現讀者寫者問題(寫者優先)
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing

pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; // it‘s mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; // it‘s mean writer can writing
pthread_mutex_t priority = PTHREAD_MUTEX_INITIALIZER; // it‘s mean writer can writing

int readCount = 0;

void* reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);

pthread_mutex_lock(&priority);
pthread_mutex_lock(&rd);
readCount++;
if( readCount == 1)
{ //first reader
pthread_mutex_lock(&wr);
}
pthread_mutex_unlock(&rd);
pthread_mutex_unlock(&priority);

printf("reader %d is reading\n", id);
sleep( rand() % 3);

pthread_mutex_lock(&rd);
readCount--;
if (readCount == 0)
{ //last reader
pthread_mutex_unlock(&wr);
}
pthread_mutex_unlock(&rd);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}

void* writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 4);
pthread_mutex_lock(&priority);
pthread_mutex_lock(&wr);
printf("\twriter is writing\n");
sleep( rand() % 4);
pthread_mutex_unlock(&wr);
pthread_mutex_unlock(&priority);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");

}

int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writer_tid;
int i;

for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
if (err != 0)
{
err_quit("can‘t create process for reader");
}
}

err = pthread_create(&writer_tid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can‘t create process for writer");
}
pause();
return 0;
}
###6 讀寫鎖
讀寫鎖適合於對數據結構的讀次數比寫次數多得多的情況.因為,讀模式鎖定時可以共享,以寫 模式鎖住時意味著獨占,所以讀寫鎖又叫共享-獨占鎖.
初始化和銷毀:
#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);
成功則返回0,出錯則返回錯誤編號. 同互斥量以上,在釋放讀寫鎖占用的內存之前,需要先通過 pthread_rwlock_destroy對讀寫鎖進行清理工作, 釋放由init分配的資源.
讀和寫:
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功則返回0,出錯則返回錯誤編號.這3個函數分別實現獲取讀鎖,獲取寫鎖和釋放鎖的操作.獲 取鎖的兩個函數是阻塞操作,同樣,非阻塞的函數為:
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功則返回0,出錯則返回錯誤編號.非阻塞的獲取鎖操作,如果可以獲取則返回0,否則返回 錯誤的EBUSY.
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing

pthread_rwlock_t lock; //it‘s mean writer can writing

int readCount = 0;

void* reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);
pthread_rwlock_rdlock(&lock);
printf("reader %d is reading\n", id);
sleep( rand() % 3);

pthread_rwlock_unlock(&lock);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}

void* writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 3);
pthread_rwlock_wrlock(&lock);
printf("\twriter is writing\n");
sleep( rand() % 3);
pthread_rwlock_unlock(&lock);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");
}

int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writerTid;
int i;

err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can‘t create process for writer");

}

pthread_rwlock_init(&lock, NULL);
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
if (err != 0)
{
err_quit("can‘t create process for reader");
}
}
pause();
pthread_rwlock_destroy(&lock);
return 0;
}
###7 條件變量 條件變量參考這裏。
與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直 到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。
條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步 的一種機制,主要包括兩個動作:一個線程等待”條件變量的條件成立”而掛起;另一個線程使 “條件成立”(給出條件成立信號)。
條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,並釋放等待 狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多 個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量 可以被用來實現這兩進程間的線程同步。
使用條件變量之前要先進行初始化。可以在單個語句中生成和初始化一個條件變量如: pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用於進程間線程的通信)。 可以利用函數pthread_cond_init動態初始化。
條件變量分為兩部分:條件和變量.條件本身是由互斥量保護的.線程在改變條件狀態前先要鎖住 互斥量.它利用線程間共享的全局變量進行同步的一種機制。
相關的函數如下:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const
timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有線程的阻塞
簡要說明:
初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;屬性置為NULL
等待條件成立.pthread_wait,pthread_timewait.wait()釋放鎖,並阻塞等待條件變量為真 timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)
激活條件變量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待線程)
清除條件變量:destroy;無線程等待,否則返回EBUSY
詳細說明
初始化:
條件變量采用的數據類型是pthread_cond_t, 在使用之前必須要進行初始化, 這包括兩種方式:
靜態: 可以把常量PTHREAD_COND_INITIALIZER給靜態分配的條件變量. 動態: pthread_cond_init函數, 是釋放動態條件變量的內存空間之前, 要用 pthread_cond_destroy對其進行清理.
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
成功則返回0, 出錯則返回錯誤編號.
當pthread_cond_init的attr參數為NULL時,會創建一個默認屬性的條件變量;非默認情況以後討論.
等待條件:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t
*restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t
*restrict mutex, const struct timespec *restrict timeout);
成功則返回0,出錯則返回錯誤編號.這兩個函數分別是阻塞等待和超時等待.
等待條件函數等待條件變為真,傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者把鎖 住的互斥量傳遞給函數.函數把調用線程放到等待條件的線程列表上,然後對互斥量解鎖,這兩個 操作是原子的. 這樣便關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時 間通道,這樣線程就不會錯過條件的任何變化.
當pthread_cond_wait返回時, 互斥量再次被鎖住.
通知條件:
#include int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);
成功則返回0, 出錯則返回錯誤編號.
這兩個函數用於通知線程條件已經滿足. 調用這兩個函數, 也稱向線程或條件發送信號. 必須註意, 一定要在改變條件狀態以後再給線程發送信號.
示例程序
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
{

pthread_cleanup_push(pthread_mutex_unlock, &mutex);

//提供函數回調保護
while (1) {

printf("thread1 is running\n");

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);

printf("thread1 applied the condition\n");

pthread_mutex_unlock(&mutex);

sleep(4);

}

pthread_cleanup_pop(0);

}


void *thread2(void *arg)
{

while (1) {

printf("thread2 is running\n");

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);

printf("thread2 applied the condition\n");

pthread_mutex_unlock(&mutex);

sleep(1);

}
}

int main()
{

pthread_t thid1, thid2;

printf("condition variable study!\n");

pthread_mutex_init(&mutex, NULL);

pthread_cond_init(&cond, NULL);

pthread_create(&thid1, NULL, (void *) thread1, NULL);

pthread_create(&thid2, NULL, (void *) thread2, NULL);

do {

pthread_cond_signal(&cond);

} while (1);

sleep(20);

pthread_exit(0);

return 0;

}
條件變量與互斥鎖、信號量的區別
互斥鎖必須總是由給它上鎖的線程解鎖,信號量的掛出即不必由執行過它的等待操作的同一 進程執行。一個線程可以等待某個給定信號燈,而另一個線程可以掛出該信號燈。
互斥鎖要麽鎖住,要麽被解開(二值狀態,類型二值信號量)。
由於信號量有一個與之關聯的狀態(它的計數值),信號量掛出操作總是被記住。然而當向 一個條件變量發送信號時,如果沒有線程等待在該條件變量上,那麽該信號將丟失。
互斥鎖是為了上鎖而設計的,條件變量是為了等待而設計的,信號燈即可用於上鎖,也可用 於等待,因而可能導致更多的開銷和更高的復雜性。
###8 信號量
信號量(semaphore)簡單好理解,不知道它有什麽缺點,在《unix 環境高級編程》中居然沒有講。我們大學課堂,上操作系統,各種PV操作,其實就是在操作信號量。
信號量的具體使用方法,請
man sem_init
相關的幾個系統調用:
sem_init
sem_wait
sem_trywait
sem_post
sem_getvalue
sem_destory
下面來看一個信號量解決哲學家就餐問題,在這裏semaphore 初始為1,用法和互斥量沒有什麽區別。
#include <semaphore.h>
#include <pthread.h>
#include "apue.h"

#define N 5 // No. of philosopher
#define M 5 // times of eating
sem_t forks[N];

void * thr_philosopher( void *arg);
int main(int argc, char* argv[])
{
int i = 0;
int err;
pthread_t tid[N];
void *tret;
//initilize semaphore
for (i = 0; i < N; i++)
{
if(sem_init(&forks[i], 0, 1) != 0)
{
err_quit("init forks error");
}
}
//create thread
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, thr_philosopher, (void *)i);
if (err != 0)
{
err_quit("can‘t create thread %d: %s\n", i + 1, strerror(err));
}
}

//get the return value
for (i = 0; i < N; i++)
{
err = pthread_join(tid[i], &tret);
if (err != 0)
{
err_quit("can‘t join with philosopher %d : %s\n", i + 1,
strerror(err));
}
printf("-------------------philosopher %d has done-------------------\n", (int)tret);
}

// delete the source of semaphore
for (i = 0; i < N; i++)
{
err = sem_destroy(&forks[i]);
if (err != 0)
{
err_sys("can‘t destory semaphore");
}
}
exit(0);
}

void * thr_philosopher( void *arg)
{

/*
* here cann‘t judge arg == NULL
* because (void *)0 will lead to arg = NULL
*/
int n = M;
int i = 0;
i = (int)arg;
while ( n-- )
{
sleep(1);
if ( i == N - 1)
{
sem_wait(&forks[0]);
sem_wait(&forks[i]);
}
else
{
sem_wait(&forks[i]);
sem_wait(&forks[i + 1]);
}
printf("philosopher %d is eating\n", i + 1);
if ( i == N - 1)
{
sem_post(&forks[0]);
sem_post(&forks[i]);
}
else
{
sem_post(&forks[i]);
sem_post(&forks[i + 1]);
}

}

return ((void*)i);
}
###9 文件互斥
文件互斥並不是操作系統提供的一組API,而是使用了一點小技巧,我們可以通過linux下文件互 斥地打開,實現線程/進程互斥的訪問資源,以此實現多線程編程。
值得註意的是,文件互斥的方式不但適用於多線程編程,還能實現多進程之間的交互。
文件互斥還有一個妙用--保證一個系統中只有一個實例,請參考這裏。
新建lock.h 文件,內容入下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void initlock(const char *lockfile);
void lock(const char *lockfile);
void unlock(const char *lockfile);
新建lock.c 文件,內容入下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void initlock(const char *lockfile)
{
int i;
unlink(lockfile);
}

void lock(const char *lockfile)
{

int fd;
while ( (fd = open(lockfile, O_RDONLY | O_CREAT | O_EXCL)) < 0)
sleep(1);
close(fd);
}

void unlock(const char *lockfile)
{
unlink(lockfile);
}
新建main.c 文件,內容如下:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
#include "lock.h"

#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};

void takeFork( int i )
{
if ( i == N - 1 )
{
lock(forks[0]);
printf("philosopher %d: takefork 0\n", i + 1 );
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
}
else
{
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
lock(forks[i+1]);
printf("philosopher %d: takefork %d\n", i + 1, i+1 );
}
}

void eating(int i, int nsecs)
{
printf("\tphilosopher %d: eat %d s\n", i + 1, nsecs );
sleep(nsecs);
}

void thinking(int i, int nsecs)
{
printf("philosopher %d: think %d sec\n", i + 1, nsecs );
sleep(nsecs);
}

void putFork( int i )
{
if ( i == N - 1 )
{
unlock(forks[0]);
unlock(forks[i]);
}
else
{
unlock(forks[i]);
unlock(forks[i+1]);
}
}

void* philosopher(void *arg)
{
int i = (int)arg;
// printf("philosopher %d : pid = %d is running", i + 1, getpid());
int m = M;
int nsecs;
srand( time(0) );
while ( m-- )
{
nsecs = rand() % 5;
thinking(i, nsecs);
takeFork(i);
nsecs = rand() % 5;
eating(i, nsecs);
putFork(i);
}
printf("====================philosopher %d : is\
successful===================\n", i + 1 );
}

int main(int argc, char* argv[])
{

int i;
int err;
pthread_t tid[N];

for (i = 0; i < sizeof(forks) / sizeof(forks[0]); i++)
{
initlock(forks[i]);
}

for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, philosopher, (void *)i);
if (err != 0)
{
err_quit("can‘t create process for philosopher");
}
}
pause();
}
最後,我們來看一個使用文件互斥實現進程之間的交互的例子,其中,lock.h 和 lock.c 請參考上面的實現。main.c 的實現如下:
#include <sys/wait.h>
#include <sys/wait.h>
#include <stdio.h>
#include "apue.h"
#include "lock.h"

#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};

void takeFork( int i )
{
if ( i == N - 1 )
{
lock(forks[0]);
printf("philosopher %d: takefork 0\n", i + 1 );
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
}
else
{
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
lock(forks[i+1]);
printf("philosopher %d: takefork %d\n", i + 1, i+1 );
}
}

void eating(int i, int nsecs)
{
printf("philosopher %d: eat %d sec\n", i + 1, nsecs );
sleep(nsecs);
}

void thinking(int i, int nsecs)
{
printf("philosopher %d: think %d sec\n", i + 1, nsecs );
sleep(nsecs);
}

void putFork( int i )
{
if ( i == N - 1 )
{
unlock(forks[0]);
unlock(forks[i]);
}
else
{
unlock(forks[i]);
unlock(forks[i+1]);
}
}

void philosopher(int i)
{
printf("philosopher %d : pid = %d\n is running", i + 1, getpid());
int m = M;
int nsecs;
srand( time(0) );
while ( m-- )
{
nsecs = rand() % 5;
thinking(i, nsecs);
takeFork(i);
nsecs = rand() % 5;
eating(i, nsecs);
putFork(i);
}
printf("==============================philosopher %d : is\
successful=============================\n", i + 1 );
}

int main(int argc, char* argv[])
{
pid_t pid;
int i;

for ( i = 0; i < N; i++ )
{
pid = fork();
if (pid == 0 || pid == -1)
{
break;
}
}

if ( pid == 0 )
{
philosopher(i);
}
else if ( pid < 0 )
{
perror("fork error");
}
else
{
waitpid(pid, 0, 0);
}

exit(0);
}
如果你是新手,可能對下面這幾行代碼不是很理解,請參考酷殼網的《一個fork的面試題》。
for ( i = 0; i < N; i++ )
{
pid = fork();
if (pid == 0 || pid == -1)
{
break;
}
}
完。

linux下多線程編程