实现信号量(二) pipe实现信号量

        上一篇开篇,实现了线程结构体。现在来开启模拟信号量吧。

        本文是用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;
}

        测试结果:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值