信号量(Semaphore)
信号量和互斥锁(mutex)的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。
不多做解释,要使用信号量同步,需要包含头文件semaphore.h。
主要用到的函数:
int sem_init(sem_t *sem, int pshared, unsigned int value);
其中sem是要初始化的信号量,pshared表示此信号量是在进程间共享还是线程间共享,
value是信号量的初始值。
int sem_destroy(sem_t *sem);,其中sem是要销毁的信号量。
只有用sem_init初始化的信号量才能用sem_destroy销毁。
int sem_wait(sem_t *sem);等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。
如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。
int sem_post(sem_t *sem); 释放信号量,让信号量的值加1。相当于V操作。
int sem_getvalue(sem_t *sem, int *sval);
取回信号量sem的当前值,把该值保存到sval中。
若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
1) 返回0
2) 返回阻塞在该信号量上的进程或线程数目
linux采用返回的第一种策略。
如果我們現在有兩個執行緒,分別負責一份工作的前半段與後半段,也就是說第一個執行緒會把它處理好的資料,發包給第二個執行緒繼續處理,而兩個執行緒的處理速度有可能不同,這種狀況我們就可以使用旗標(Semaphore)的方式來串接。
旗標本身就是一個計數器,也就是紀錄目前尚未處理的工作數量,我們可以使用 sem_wait
來判斷是否有尚未處理的工作,當工作數量大於 0
時,sem_wait
就會讓執行緒進入處理,並且把工作數量遞減 1
,而如果工作數量為 0
的時候,則會讓執行緒等待,直到有新的工作來臨時,才讓執行緒進入。
另外在產生工作的執行緒中,可以使用 sem_post
放入新的工作(也就讓將計數器遞增 1
),這樣就可以將多個執行緒串接起來處理大型的工作流程。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t semaphore; // 旗標
int counter = 0;
// 子執行緒函數
void* child() {
for(int i = 0;i < 5;++i) {
sem_wait(&semaphore); // 等待工作
printf("Counter = %d\n", ++counter);
sleep(1);
}
pthread_exit(NULL);
}
// 主程式
int main(void) {
// 初始化旗標,僅用於本行程,初始值為 0
sem_init(&semaphore, 0, 0);
pthread_t t;
pthread_create(&t, NULL, child, NULL);
// 送出兩個工作
printf("Post 2 jobs.\n");
sem_post(&semaphore);
sem_post(&semaphore);
sleep(4);
// 送出三個工作
printf("Post 3 jobs.\n");
sem_post(&semaphore);
sem_post(&semaphore);
sem_post(&semaphore);
pthread_join(t, NULL);
sem_destroy(&semaphore);
return 0;
}
执行结果:
Post 2 jobs.
Counter = 1
Counter = 2
Post 3 jobs.
Counter = 3
Counter = 4
Counter = 5
在這個程式中,主執行緒負責派送工作,工作有時候多、有時候少,而子執行緒則是以每秒處理一個工作的速度,消化接收到的工作。
旗標在使用前要先以 sem_init
初始化,其第二個參數是指定是否要讓其他的行程(process)共用旗標,這裡我們是單一行程、多執行緒的程式,所以第二個參數設定為 0
即可;第三個參數則是設定旗標的初始值。
旗標本身只是紀錄工作的數量,並且控制執行緒的執行,並沒有負責資料的配送,通常我們可以自己實做一個資料佇列(queue),配合旗標來計算索引,讓子執行緒從佇列中取得資料進行處理。
参考:https://blog.gtwang.org/programming/pthread-multithreading-programming-in-c-tutorial/