上一篇开篇,实现了线程结构体。现在来开启模拟信号量吧。
本文是用pipe管道来实现信号量。
用管道实现信号量的原理是:当管道里没有内容时,调用read函数读,会造成线程的阻塞。用pv操作来描述的话,当用户调用p操作申请一个资源时,内部实现是调用read函数读取管道里的一个字符。如果能读取,就马上返回。否则阻塞线程。当用户调用v操作释放一个资源时,内部实现是往管道写入一个字符。
pipe_sem.hpp文件
#ifndef PIPE_SEM_HPP
#define PIPE_SEM_HPP
#include<pthread.h>
typedef struct pipe_sem_tag
{
int fd[2];
pthread_mutex_t mutex;
int valid; //同线程结构体中的一样。
}pipe_sem_t;
//num是信号量的初始值
int pipe_sem_init(pipe_sem_t* pip, int num);
int pipe_sem_p(pipe_sem_t* pip);
int pipe_sem_tryP(pipe_sem_t* pip);
int pipe_sem_v(pipe_sem_t* pip);
int pipe_sem_destroy(pipe_sem_t* pip);
#endif // PIPE_SEM_HPP
pipe_sem.cpp文件
#include"pipe_sem.hpp"
#include<sys/time.h>
#include<sys/select.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>
#define PIPE_SEM_VALID 0x78da
//用管道实现信号量的原理是:当管道里没有内容时,调用read函数读,会造成线程的阻塞
//用pv操作来描述的话,当用户调用p操作申请一个资源时,内部实现是调用read函数读取
//管道里的一个字符。如果能读取,就马上返回。否则阻塞线程。
//当用户调用v操作释放一个资源时,内部实现是往管道写入一个字符。
//这个函数不是线程安全的。本系列文章中的所有init函数都不是线程安全的。
//使用者应该在创建多个线程前就初始化好这些结构体。
int pipe_sem_init(pipe_sem_t* pip, int num)
{
int status;
if( pip == NULL || num < 0 )
return EINVAL;
status = pipe(pip->fd);
if( status != 0 )
return errno;
status = pthread_mutex_init(&pip->mutex, NULL);
if( status != 0 )
goto error;
//通过向管道写入num个字符,模拟信号量的初始值为num
while( num != 0 )
{
status = write(pip->fd[1], "v", 1);;
if( status != 1 )
goto error;
--num;
}
pip->valid = PIPE_SEM_VALID;
return 0;
error:
close(pip->fd[0]); //ignore the error
close(pip->fd[1]); //ignore the error
return status;
}
void cleanup_unlock(void* arg)
{
pthread_mutex_t *mutex = (pthread_mutex_t*)arg;
pthread_mutex_unlock(mutex);
}
int pipe_sem_p(pipe_sem_t* pip)
{
static char ch;
int status, rt;
if( pip == NULL || pip->valid != PIPE_SEM_VALID)
return EINVAL;
//when one thread run the this function, another thread run the
//pipe_sem_tryP, it will be wrong if don't use the mutex.
//because pipe_sem_tryP function will change to fd[0] to non block
status = pthread_mutex_lock(&pip->mutex); //得加锁
if( status != 0 )
return status;
//因为read是可取消点。所以当线程在read中睡眠的时候,
//其他线程调用thread_cancel取消这个睡眠的线程时,
//睡眠的线程将苏醒,然后继续锁住mutex, 之后就退出终止。
//所以,要设定一个清理函数,发生这种情况时,在清理函数中解锁。
pthread_cleanup_push(cleanup_unlock, &pip->mutex);
while( (status = read(pip->fd[0], &ch, 1) ) == -1 )
{
if( errno == EINTR ) //被一个信号中断。遵循POSIX.1标准OS的不会这种情况
continue;
else
break;
}
if( status == - 1)
rt = errno;
else if( status == 0 ) //the end of pipe。即管道的写端被关闭了
rt = EPIPE;
else
rt = 0;
pthread_cleanup_pop(0);
pthread_mutex_unlock(&pip->mutex); //ignore the error
return rt;
}
//通过fcntl函数把文件描述字fd[1]修改为非阻塞的。从而实现尝试功能。
int pipe_sem_tryP(pipe_sem_t* pip)
{
int status, rt;
int flags = 0;
static char ch;
if( pip == NULL || pip->valid != PIPE_SEM_VALID )
return EINVAL;
//when one thread run the this function, another thread run the
//pipe_sem_p, it will be wrong if don't use the mutex.
//because this function will change to fd[0] to non block
status = pthread_mutex_lock(&pip->mutex);
if( status != 0 )
return status;
pthread_cleanup_push(cleanup_unlock, &pip->mutex);
if( (flags = fcntl(pip->fd[0], F_GETFL, 0)) < 0 )
{
pthread_mutex_unlock(&pip->mutex);
return errno;
}
flags |= O_NONBLOCK; //turn it to non block
if( (flags = fcntl(pip->fd[0], F_SETFL, flags)) < 0 )
{
pthread_mutex_unlock(&pip->mutex);
return errno;
}
status = read(pip->fd[0], &ch, 1);
if( status == 0 ) //the end of pipe
rt = EPIPE;
else if( status > 0 )
rt = 0;
else
rt = errno;
pthread_cleanup_pop(0);
error:
flags &= ~O_NONBLOCK; //turn it to block
fcntl(pip->fd[0], F_SETFL, flags);
pthread_mutex_unlock(&pip->mutex);
return rt;
}
int pipe_sem_v(pipe_sem_t* pip)
{
if( pip == NULL || pip->valid != PIPE_SEM_VALID)
return EINVAL;
return write(pip->fd[1], "v", 1);
}
int pipe_sem_destroy(pipe_sem_t* pip)
{
if( pip == NULL || pip->valid != PIPE_SEM_VALID)
return EINVAL;
close(pip->fd[0]); //ignore the error
close(pip->fd[1]); //ignore the error
pip->valid = 0;
return pthread_mutex_destroy(&pip->mutex);
}
#include "pipe_sem.hpp"
#include"Thread.hpp"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define NBUFF 8
#define BUFFSIZE 4096
struct { /* data shared by producer and consumer */
struct {
char data[BUFFSIZE]; /* a buffer */
ssize_t n; /* count of #bytes in the buffer */
} buff[NBUFF]; /* NBUFF of these buffers/counts */
pipe_sem_t nempty, nfull; /* semaphores, not pointers */
pipe_sem_t writer_mutex, reader_mutex;
} shared;
int writer_index = 0, reader_index = 0;
int fd; /* input file to copy to stdout */
void* produce(void *), *consume(void *);
void* produce_tryP(void *arg);
//运行测试程序时要输入一个文件名作为参数
int main(int argc, char **argv)
{
Thread_t tid_produce1, tid_produce2, tid_produce3;
Thread_t tid_consume1, tid_consume2;
if (argc != 2)
{
printf("use <pathname> as pramater \n");
exit(1);
}
fd = open(argv[1], O_RDONLY);
if( fd == -1 )
{
printf("cann't open the file\n");
return -1;
}
pipe_sem_init(&shared.writer_mutex, 1);
pipe_sem_init(&shared.reader_mutex, 1);
pipe_sem_init(&shared.nempty, NBUFF);
pipe_sem_init(&shared.nfull, 0);
thread_init(&tid_produce1);
thread_init(&tid_produce2);
thread_init(&tid_produce3);
thread_init(&tid_consume1);
thread_init(&tid_consume2);
thread_create(&tid_consume1, NULL, consume);
thread_create(&tid_consume2, NULL, consume);
thread_create(&tid_produce1, NULL, produce);
thread_create(&tid_produce2, NULL, produce);
thread_create(&tid_produce3, NULL, produce_tryP);
thread_start(&tid_consume1, NULL);
thread_start(&tid_consume2, NULL);
thread_start(&tid_produce1, NULL);
thread_start(&tid_produce2, NULL);
thread_start(&tid_produce3, NULL);
thread_join(&tid_consume1, NULL);
thread_join(&tid_consume2, NULL);
thread_join(&tid_produce1, NULL);
thread_join(&tid_produce2, NULL);
thread_join(&tid_produce3, NULL);
thread_destroy(&tid_consume1);
thread_destroy(&tid_consume2);
thread_destroy(&tid_produce1);
thread_destroy(&tid_produce2);
thread_destroy(&tid_produce3);
pipe_sem_destroy(&shared.writer_mutex);
pipe_sem_destroy(&shared.reader_mutex);
pipe_sem_destroy(&shared.nempty);
pipe_sem_destroy(&shared.nfull);
exit(0);
}
void *produce(void *arg)
{
while( 1 )
{
pipe_sem_p(&shared.nempty); /* wait for at least 1 empty slot */
//这个本来可以用互斥量的。不过为了多测试pipe_sem_t,就使用信号量了
pipe_sem_p(&shared.writer_mutex);
//读取文件的内容
shared.buff[writer_index].n = read(fd, shared.buff[writer_index].data, BUFFSIZE);
//文件的内容已经读取完毕。
if( shared.buff[writer_index].n == 0 )
{
//离开前,要记得解锁
pipe_sem_v(&shared.nfull);
pipe_sem_v(&shared.writer_mutex);
return NULL;
}
writer_index = (writer_index+1)%NBUFF; //循环使用缓冲区
pipe_sem_v(&shared.nfull);
pipe_sem_v(&shared.writer_mutex);
}
return NULL;
}
void* produce_tryP(void *arg)
{
int status;
while( 1 )
{
/* wait for at least 1 empty slot */
while( 1 )
{
status = pipe_sem_tryP(&shared.nempty);
if( status == 0 )
break;
else if( status == EAGAIN )
{
usleep(10*1000); //sleep 10 毫秒
continue;
}
else
return NULL;
}
pipe_sem_p(&shared.writer_mutex);
shared.buff[writer_index].n = read(fd, shared.buff[writer_index].data, BUFFSIZE);
if( shared.buff[writer_index].n == 0 )
{
pipe_sem_v(&shared.nfull);
pipe_sem_v(&shared.writer_mutex);
return NULL;
}
writer_index = (writer_index+1)%NBUFF;
pipe_sem_v(&shared.nfull);
pipe_sem_v(&shared.writer_mutex);
}
return NULL;
}
void* consume(void *arg)
{
while( 1 )
{
pipe_sem_p(&shared.nfull);
pipe_sem_p(&shared.reader_mutex);
//缓冲区里面没有数据了。
if( shared.buff[reader_index].n == 0)
{
pipe_sem_v(&shared.nempty);
pipe_sem_v(&shared.reader_mutex);
return NULL;
}
write(STDOUT_FILENO, shared.buff[reader_index].data, shared.buff[reader_index].n);
reader_index = (reader_index+1)%NBUFF;
pipe_sem_v(&shared.nempty);
pipe_sem_v(&shared.reader_mutex);
}
return NULL;
}
测试结果: