TDengine代碼學習(1):生產者與消費者模式

代碼介紹

學習TDengine tsched.c 中的代碼,這是多個生產者和消費者的模式,所以基於單個生產者和消費者的忙等待方式是不適合的,會消耗cpu資源,必須要用到信號量。

具體的實現方式:

  • 使用兩個信號量和一個互斥鎖。
  • 互斥鎖 queueMutex 用來保護queue中的數據, 生產者和消費者在訪問queue前都需要獲取這個互斥鎖。
  • 信號量 emptySem 表示queue中是否有空的位置,可以放入新數據。
  • 信號量 fullSem 表示queue中是否有數據,可以被讀取。
  • 每個消費者和生產者都是一個單獨的線程。

生產者的處理循環圖如下:
生產者
消費者的處理循環圖如下:
消費者
對原來的代碼進行了簡單的修改,用來測試代碼。

測試效果

爲了能夠看到正確的線程執行順序,不能直接使用printf函數,所以使用一個全局的字符數組record,在每個線程讀寫數據的時候向這個全局數組添加內容。
在這裏插入圖片描述

完整測試代碼

在linux 下面運行測試。
編譯需要加上 -lpthread 選項。例子如下:
gcc -o producer_consumer producer_consumer.c -lpthread

#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>

typedef struct
{
	char data[16];
	int num;
}SSchedMsg;

typedef struct 
{
	sem_t			emptySem;
	sem_t			fullSem;
	pthread_mutex_t	queueMutex;
	int				fullSlot;
	int				emptySlot;
	int				queueSize;
	int				numOfThreads;
	SSchedMsg *		queue;
	pthread_t *		qthread;
} SSchedQueue;

#define RECORD_LEN 1024
static char	record[RECORD_LEN];
static int	record_len = RECORD_LEN-1;
static char * ptr;
static int done = 0;

void initRecord()
{
	memset(record, 0, RECORD_LEN);
	ptr = (char *)record; 
}

/* type value 0:producer, 1:consumer */
void recordAction(int tid, int type, int num)
{
	int len;

	if(record_len <= 0)
	{
		done = 1;
		return;
	}

	if(type == 0)
		len = snprintf(ptr, record_len, "Producer[%d] set queue index[%d]\n",tid, num);
	else
		len = snprintf(ptr, record_len, "\t\tConsumer[%d] get queue index[%d]\n", tid, num);
	record_len -= len;
	ptr += len;		
}

void * produceSchedQueue(void * param);
void * consumeSchedQueue(void *param);
void cleanupScheduler(void *param);

void * initScheduler(int queueSize, int numOfThreads)
{
	int i = 0;
	pthread_attr_t attr;
	SSchedQueue * pSched = (SSchedQueue *)malloc(sizeof(SSchedQueue));
	
	if(pSched == NULL) return NULL;

	memset(pSched, 0, sizeof(SSchedQueue));
	pSched->queueSize = queueSize;
	pSched->numOfThreads = numOfThreads;

	if(pthread_mutex_init(&pSched->queueMutex, NULL) < 0)
	{
		printf("init queueMutex failed, reason:%s", strerror(errno));
		goto Error;
	}

	if(sem_init(&pSched->emptySem, 0, (unsigned int)pSched->queueSize) != 0)
	{
		printf("init empty semaphore failed, reason:%s", strerror(errno));
		goto Error;
	}

	if(sem_init(&pSched->fullSem, 0, 0) != 0)
	{
		printf("init full semaphore failed, reason:%s", strerror(errno));
		goto Error;
	}

	if((pSched->queue = (SSchedMsg *)malloc((size_t)pSched->queueSize * sizeof(SSchedMsg))) == NULL)
	{
		printf("no enough memory for queue, reason:%s", strerror(errno));
		goto Error;
	}
	memset(pSched->queue, 0, (size_t)pSched->queueSize * sizeof(SSchedMsg));
	
	pSched->fullSlot = 0;
	pSched->emptySlot = 0;
	pSched->qthread = malloc(sizeof(pthread_t) * (size_t)pSched->numOfThreads);

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	for(i = 0; i < pSched->numOfThreads; i++)
	{
		void * (*func)(void *);
		if(i % 2 == 0)
			func = consumeSchedQueue;
		else
			func = produceSchedQueue;
		
		if(pthread_create(pSched->qthread + i, &attr, func, (void *)pSched) != 0)
		{
			printf("failed to create rpc thread[%d], reason:%s", i, strerror(errno));
			goto Error;
		}
	}
	printf("scheduler is initialized, numOfThreads:%d\n", pSched->numOfThreads);
	return (void *)pSched;

Error:
	cleanupScheduler((void *)pSched);
	return NULL;
}

pid_t gettid()
{
     return syscall(SYS_gettid);
}

void * consumeSchedQueue(void * param)
{
	SSchedMsg msg;
	SSchedQueue * pSched = (SSchedQueue *)param;
	pid_t tid = gettid();

	if(pSched == NULL)
	{
		printf("SSchedQueue is NULL");
		return NULL;
	}
	
	while(1)
	{
		if(sem_wait(&pSched->fullSem) != 0)
		{
			printf("wait fullSem failed, errno:%d, reason:%s", errno, strerror(errno));
			if(errno == EINTR)
				continue;
		}

		if(pthread_mutex_lock(&pSched->queueMutex) != 0)
			printf("lock queueMutex failed, reason:%s", strerror(errno));

		msg = pSched->queue[pSched->fullSlot];
		memset(pSched->queue + pSched->fullSlot, 0, sizeof(SSchedMsg));
		pSched->fullSlot = (pSched->fullSlot + 1) % pSched->queueSize;

		recordAction(tid, 1, msg.num);
		
		if(pthread_mutex_unlock(&pSched->queueMutex) != 0)
			printf("unlock queueMutex failed, reason:%s\n", strerror(errno));

		if(sem_post(&pSched->emptySem) != 0)
			printf("post emptySem failed, reason:%s\n", strerror(errno));

		usleep(10000);
	}
}

void * produceSchedQueue(void * param)
{
	SSchedMsg msg;
	SSchedQueue * pSched = (SSchedQueue *)param;
	pid_t tid = gettid();

	if(pSched == NULL)
	{
		printf("SSchedQueue is NULL");
		return NULL;
	}

	memset(&msg, 0, sizeof(SSchedMsg));

	while(1)
	{
		if(sem_wait(&pSched->emptySem) != 0)
		{
			printf("wait emptySem failed, errno:%d, reason:%s", errno, strerror(errno));
			if(errno == EINTR)
				continue;
		}

		if(pthread_mutex_lock(&pSched->queueMutex) != 0)
			printf("lock queueMutex failed, reason:%s", strerror(errno));

		msg.num = pSched->emptySlot;
		pSched->queue[pSched->emptySlot] = msg;
		pSched->emptySlot = (pSched->emptySlot + 1) % pSched->queueSize;

		recordAction(tid, 0, msg.num);
		
		if(pthread_mutex_unlock(&pSched->queueMutex) != 0)
			printf("unlock queueMutex failed, reason:%s\n", strerror(errno));

		if(sem_post(&pSched->fullSem) != 0)
			printf("post fullSem failed, reason:%s\n", strerror(errno));

		usleep(10000);
	}
}

void cleanupScheduler(void * param)
{
	int i = 0;
	SSchedQueue * pSched = (SSchedQueue *)param;

	if(pSched == NULL) return;

	for( i = 0; i < pSched->numOfThreads; i++)
	{
		pthread_cancel(pSched->qthread[i]);
		pthread_join(pSched->qthread[i], NULL);
	}

	sem_destroy(&pSched->emptySem);
	sem_destroy(&pSched->fullSem);
	pthread_mutex_destroy(&pSched->queueMutex);

	free(pSched->qthread);
	free(pSched->queue);
	free(pSched);
}

int main()
{
	initRecord();
	initScheduler(6, 8);
	while(1)
	{
		if(done)
		{
			printf("%s", record);
			break;
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章