1. 程式人生 > >Linux多執行緒——使用訊號量同步執行緒

Linux多執行緒——使用訊號量同步執行緒

訊號量、同步這些名詞在程序間通訊時就已經說過,在這裡它們的意思是相同的,只不過是同步的物件不同而已。但是下面介紹的訊號量的介面是用於執行緒的訊號量,注意不要跟用於程序間通訊的訊號量混淆,關於用於程序間通訊的訊號量的詳細介紹可以參閱我的另一篇博文:。相似地,執行緒同步是控制執行緒執行和訪問臨界區域的方法一、什麼是訊號量 執行緒的訊號量與程序間通訊中使用的訊號量的概念是一樣,它是一種特殊的變數,它可以被增加或減少,但對其的關鍵訪問被保證是原子操作。如果一個程式中有多個執行緒試圖改變一個訊號量的值,系統將保證所有的操作都將依次進行。 而只有0和1兩種取值的訊號量叫做二進位制訊號量,在這裡將重點介紹。而訊號量一般常用於保護一段程式碼,使其每次只被一個執行執行緒執行。我們可以使用二進位制訊號量來完成這個工作。 二、訊號量的介面和使用 訊號量的函式都以sem_開頭,執行緒中使用的基本訊號量函式有4個,它們都宣告在標頭檔案semaphore.h
中。 1、sem_init函式 該函式用於建立訊號量,其原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
該函式初始化由sem指向的訊號物件,設定它的共享選項,並給它一個初始的整數值。pshared控制訊號量的型別,如果其值為0,就表示這個訊號量是當前程序的區域性訊號量,否則訊號量就可以在多個程序之間共享,value為sem的初始值。呼叫成功時返回0,失敗返回-1. 2、sem_wait函式 該函式用於以原子操作的方式將訊號量的值減1。原子操作就是,如果兩個執行緒企圖同時給一個訊號量加1或減1,它們之間不會互相干擾。它的原型如下:
int sem_wait(sem_t *sem);
sem指向的物件是由sem_init呼叫初始化的訊號量。呼叫成功時返回0,失敗返回-1. 3、sem_post函式 該函式用於以原子操作的方式將訊號量的值加1。它的原型如下:
int sem_post(sem_t *sem);
與sem_wait一樣,sem指向的物件是由sem_init呼叫初始化的訊號量。呼叫成功時返回0,失敗返回-1. 4、sem_destroy函式 該函式用於對用完的訊號量的清理。它的原型如下:
int sem_destroy(sem_t *sem);
成功時返回0,失敗時返回-1. 三、使用訊號量同步執行緒 下面以一個簡單的多執行緒程式來說明如何使用訊號量進行執行緒同步。在主執行緒中,我們建立子執行緒,並把陣列msg作為引數傳遞給子執行緒,然後主執行緒等待直到有文字輸入,然後呼叫sem_post來增加訊號量的值,這樣就會立刻使子執行緒從sem_wait的等待中返回並開始執行。執行緒函式在把字串的小寫字母變成大寫並統計輸入的字元數量之後,它再次呼叫sem_wait並再次被阻塞,直到主執行緒再次呼叫sem_post增加訊號量的值。
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//執行緒函式
void *thread_func(void *msg);
sem_t sem;//訊號量

#define MSG_SIZE 512

int main()
{
	int res = -1;
	pthread_t thread;
	void *thread_result = NULL;
	char msg[MSG_SIZE];
	//初始化訊號量,其初值為0
	res = sem_init(&sem, 0, 0);
	if(res == -1)
	{
		perror("semaphore intitialization failed\n");
		exit(EXIT_FAILURE);
	}
	//建立執行緒,並把msg作為執行緒函式的引數
	res = pthread_create(&thread, NULL, thread_func, msg);
	if(res != 0)
	{
		perror("pthread_create failed\n");
		exit(EXIT_FAILURE);
	}
	//輸入資訊,以輸入end結束,由於fgets會把回車(\n)也讀入,所以判斷時就變成了“end\n”
	printf("Input some text. Enter 'end'to finish...\n");
	while(strcmp("end\n", msg) != 0)
	{
		fgets(msg, MSG_SIZE, stdin);
		//把訊號量加1
		sem_post(&sem);
	}

	printf("Waiting for thread to finish...\n");
	//等待子執行緒結束
	res = pthread_join(thread, &thread_result);
	if(res != 0)
	{
		perror("pthread_join failed\n");
		exit(EXIT_FAILURE);
	}
	printf("Thread joined\n");
	//清理訊號量
	sem_destroy(&sem);
	exit(EXIT_SUCCESS);
}

void* thread_func(void *msg)
{
	//把訊號量減1
	sem_wait(&sem);
	char *ptr = msg;
	while(strcmp("end\n", msg) != 0)
	{
		int i = 0;
		//把小寫字母變成大寫
		for(; ptr[i] != '\0'; ++i)
		{
			if(ptr[i] >= 'a' && ptr[i] <= 'z')
			{
				ptr[i] -= 'a' - 'A';
			}
		}
		printf("You input %d characters\n", i-1);
		printf("To Uppercase: %s\n", ptr);
		//把訊號量減1
		sem_wait(&sem);
	}
	//退出執行緒
	pthread_exit(NULL);
}
執行結果如下:
從執行的結果來看,這個程式的確是同時在執行兩個執行緒,一個控制輸入,另一個控制處理統計和輸出。 四、分析此訊號量同步程式的缺陷 但是這個程式有一點點的小問題,就是這個程式依賴接收文字輸入的時間足夠長,這樣子執行緒才有足夠的時間在主執行緒還未準備好給它更多的單詞去處理和統計之前處理和統計出工作區中字元的個數。所以當我們連續快速地給它兩組不同的單詞去統計時,子執行緒就沒有足夠的時間支執行,但是訊號量已被增加不止一次,所以字元統計執行緒(子執行緒)就會反覆處理和統計字元數目,並減少訊號量的值,直到它再次變成0為止。 為了更加清楚地說明上面所說的情況,修改主執行緒的while迴圈中的程式碼,如下:
	printf("Input some text. Enter 'end'to finish...\n");
	while(strcmp("end\n", msg) != 0)
	{
		if(strncmp("TEST", msg, 4) == 0)
		{
			strcpy(msg, "copy_data\n");
			sem_post(&sem);
		}
		fgets(msg, MSG_SIZE, stdin);
		//把訊號量加1
		sem_post(&sem);
	}
重新編譯程式,此時執行結果如下:

當我們輸入TEST時,主執行緒向子執行緒提供了兩個輸入,一個是來自鍵盤的輸入,一個來自主執行緒複數據到msg中,然後從執行結果可以看出,執行出現了異常,沒有處理和統計從鍵盤輸入TEST的字串而卻對複製的資料作了兩次處理。原因如上面所述。 五、解決此缺陷的方法 解決方法有兩個,一個就是再增加一個訊號量,讓主執行緒等到子執行緒處理統計完成之後再繼續執行;另一個方法就是使用互斥量。 下面給出用增加一個訊號量的方法來解決該問題的程式碼,原始檔名為semthread2.c,原始碼如下:
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


//執行緒函式
void *thread_func(void *msg);
sem_t sem;//訊號量
sem_t sem_add;//增加的訊號量


#define MSG_SIZE 512


int main()
{
	int res = -1;
	pthread_t thread;
	void *thread_result = NULL;
	char msg[MSG_SIZE];
	//初始化訊號量,初始值為0
	res = sem_init(&sem, 0, 0);
	if(res == -1)
	{
		perror("semaphore intitialization failed\n");
		exit(EXIT_FAILURE);
	}
	//初始化訊號量,初始值為1
	res = sem_init(&sem_add, 0, 1);
	if(res == -1)
	{
		perror("semaphore intitialization failed\n");
		exit(EXIT_FAILURE);
	}
	//建立執行緒,並把msg作為執行緒函式的引數
	res = pthread_create(&thread, NULL, thread_func, msg);
	if(res != 0)
	{
		perror("pthread_create failed\n");
		exit(EXIT_FAILURE);
	}
	//輸入資訊,以輸入end結束,由於fgets會把回車(\n)也讀入,所以判斷時就變成了“end\n”
	printf("Input some text. Enter 'end'to finish...\n");
	
	sem_wait(&sem_add);
	while(strcmp("end\n", msg) != 0)
	{
		if(strncmp("TEST", msg, 4) == 0)
		{
			strcpy(msg, "copy_data\n");
			sem_post(&sem);
			//把sem_add的值減1,即等待子執行緒處理完成
			sem_wait(&sem_add);
		}
		fgets(msg, MSG_SIZE, stdin);
		//把訊號量加1
		sem_post(&sem);
		//把sem_add的值減1,即等待子執行緒處理完成
		sem_wait(&sem_add);
	}


	printf("Waiting for thread to finish...\n");
	//等待子執行緒結束
	res = pthread_join(thread, &thread_result);
	if(res != 0)
	{
		perror("pthread_join failed\n");
		exit(EXIT_FAILURE);
	}
	printf("Thread joined\n");
	//清理訊號量
	sem_destroy(&sem);
	sem_destroy(&sem_add);
	exit(EXIT_SUCCESS);
}


void* thread_func(void *msg)
{
	char *ptr = msg;
	//把訊號量減1
	sem_wait(&sem);
	while(strcmp("end\n", msg) != 0)
	{
		int i = 0;
		//把小寫字母變成大寫
		for(; ptr[i] != '\0'; ++i)
		{
			if(ptr[i] >= 'a' && ptr[i] <= 'z')
			{
				ptr[i] -= 'a' - 'A';
			}
		}
		printf("You input %d characters\n", i-1);
		printf("To Uppercase: %s\n", ptr);
		//把訊號量加1,表明子執行緒處理完成
		sem_post(&sem_add);
		//把訊號量減1
		sem_wait(&sem);
	}
	sem_post(&sem_add);
	//退出執行緒
	pthread_exit(NULL);
}
其執行結果如下:
分析:這裡我們多使用了一個訊號量sem_add,並把它的初值賦為1,在主執行緒在使用sem_wait來等待子執行緒處理完全,由於它的初值為1,所以主執行緒第一次呼叫sem_wait總是立即返回,而第二次呼叫則需要等待子執行緒處理完成之後。而在子執行緒中,若處理完成就會馬上使用sem_post來增加訊號量的值,使主執行緒中的sem_wait馬上返回並執行緊接下面的程式碼。從執行結果來看,執行終於正常了。注意,線上程函式中,訊號量sem和sem_add使用sem_wait和sem_post函式的次序,它們的次序不能錯亂,否則在輸入end時,可能執行不正常,子執行緒不能正常退出,從而導致程式不能退出。

相關推薦

Linux執行——使用訊號同步執行

訊號量、同步這些名詞在程序間通訊時就已經說過,在這裡它們的意思是相同的,只不過是同步的物件不同而已。但是下面介紹的訊號量的介面是用於執行緒的訊號量,注意不要跟用於程序間通訊的訊號量混淆,關於用於程序間通訊的訊號量的詳細介紹可以參閱我的另一篇博文:。相似地,執行緒同步是控制執

Linux利用訊號實現執行同步與互斥

執行緒使用互斥鎖可以實現執行緒間的互斥,而互斥鎖本身就是對資源的一種標識狀態,當可以申請到鎖時說明此時資源可以使用,當申請鎖失敗時說明資源此時被其他執行緒所佔用不可使用,我們可以使用訊號量來代替互斥鎖實現。 訊號量用來表示資源數目,當一個執行緒要去訪問資源時,必須先去申請

Linux執行——使用互斥同步執行

前文再續,書接上一回,在上一篇文章:Linux多執行緒——使用訊號量同步執行緒中,我們留下了一個如何使用互斥量來進行執行緒同步的問題,本文將會給出互斥量的詳細解說,並用一個互斥量解決上一篇文章中,要使用兩個訊號量才能解決的只有子執行緒結束了對輸入的處理和統計後,主執行緒才能

Linux下從訊號執行排程時間

前幾天寫了一篇文章關於Linux下程序排程時間的,本意是想測試下實時效能的,包括中斷響應時間等等,這個可能需要藉助於硬體發出終端來測試, 那片文章是講的是通過傳送訊號給另一個程序,然後測量傳送訊號到進入訊號處理程式之間的時間 訊號只是針對程序來說的,今天

執行訊號,互斥鎖,條件變數異同

條件變數類似於訊號量,一旦條件“成熟”則進行處理,條件變數能使執行緒處於wait狀態,另一個執行緒在滿足條件的時候發signal,然後所有處於wait的執行緒依次得到喚醒,條件變數通過執行緒阻塞和可以給另一個執行緒傳送signal喚醒執行緒的優厚條件彌補了互斥鎖只有加鎖和解鎖的這兩種情況,在一般情況下,條件變

執行訊號semaphore

需求說明:6輛車搶佔三個停車位 package hello_java; import java.util.Random; import java.util.concurrent.Semaphore; public class Tool03 { public static void

Hystrix 服務的隔離策略對比,訊號執行池隔離的差異

支援的隔離策略 Hystrix支援的 hytrix支援執行緒池隔離和訊號量隔離 訊號量的隔離:  it executes on the calling thread and concurrent requests are limited by the semaphore count  - 引自

訊號sem_wait 執行訊號相關操作

SYNOPSIS        #include <semaphore.h>        int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化訊號量        int sem_wa

Visual C++利用互斥同步執行實現檔案讀取進度條

忘了原文的位置了。 一、前言         檔案讀取進度條的實現可以有很多種方法,常用的是在讀取檔案的過程中隔一定時間向對話方塊傳送訊息以控制進度條的位置,但是這種方法很難確定隔多少時問傳送一個訊息,因為檔案的大小是不確定的,時間間隔長了可能檔案已經讀取完了還沒有傳送訊

Linux 訊號同步實驗

題目:有一個盤子,父親放入蘋果,母親放入桔子,女兒取出蘋果,兒子取出桔子。 同步關係:父親放蘋果和女兒取蘋果 && 母親放桔子和兒子取桔子 互斥關係:父親放蘋果和母親放桔子 下面是原始碼: #include <stdio.h> #include

【2017-06-20】Linux應用開發工程師C/C++面試問題之一:Linux線程程序的同步問題

依次 其它 如果 開發工程師 logs 特殊 另一個 特殊情況 發生 參考之一:Linux 線程同步的三種方法 鏈接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 簡要回答: Linux下線程同步最常用的三種方法就是互斥鎖、

linux下用信號同步線程

amp serve 計數 thread roc 統計 ida 線程鎖 linu linux下利用信號量同步線程實現線程訪問計數功能 這裏是核心代碼,其他參考IPC一個綜合小實踐 #include <sys/types.h> #include <unist

python 64式: 第15式、程序,訊號與程序間通訊

#!/usr/bin/env python # -*- coding: utf-8 -*- import multiprocessing import time ''' 關鍵 1 訊號量 multiprocessing.Semaphore(n): n表示資源總數 作用: 控制對共享資源的訪問數量

LInux系統中使用訊號對兩個程序中訪問同一資源互斥注意事項

對於同一個共享資源的訪問,在Linux系統應用程式設計中經常遇到,通過訊號量來保護共享資源,實現互斥非常重要,對於實現互斥一般有如下幾個步驟: (1):建立訊號量:呼叫 int semget(key_t key,int num,in

linux程序控制之訊號 semget,semctl,semop

轉載自 https://www.cnblogs.com/52php/p/5851570.html 這篇文章將講述別一種程序間通訊的機制——訊號量。注意請不要把它與之前所說的訊號混淆起來,訊號與訊號量是不同的兩種事物。有關訊號的更多內容,可以閱讀我的另一篇文章:L

Python協程使用Semaphore訊號同步機制限制併發

from aiohttp import ClientSession import asyncio ###################### # 限制協程併發量 ###################### async def hello(sem, url): asyn

Linux併發(POSIX訊號

System-V的訊號量是老古董,除非萬不得已,否則我們一般用POSIX訊號量,好用、簡單、靠譜。 拓展: POSIX訊號量分為兩種,分別是POSIX無名訊號量和POSIX有名訊號量,這兩種訊號量比之前介紹的system-V的訊號量機制要簡潔,雖然沒有後者的應用

linux程序間通訊-訊號

引入訊號量之前,先介紹幾個概念: 1.臨界資源:同一時刻只允許一個程序或執行緒訪問的資源。(有時候是有限個程序或執行緒訪問),就比如一支筆一次只能是一個人使用。 這裡的臨界資源在c語言或c++中的表

訊號同步互斥問題

一、訊號量 訊號量是一種抽象資料型別 由一個整型(sem)變數和兩個原子操作組成 P():sem減1,如果sem<0進入等待狀態,否則繼續 V():sem加1,如果sem<=0則喚醒一

Linux C++Timer(用訊號實現)

#include <stdio.h> #include <time.h> #include <sys/time.h> #include <stdlib.h> #include <signal.h>