代碼介紹
學習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;
}
}
}