Linux系统编程过渡:管道实例(Linux系统编程第5-6部分过度)

 主要内容:

这个例子可以作为我们完成前面并发、多线程等内容,再到后续的进程间通信的一个过度。为后续我们内核提供的管道机制做铺垫。

回想我们前面写过无数次的例子:筛质数

在之前的筛质数程序中,我们在进程阶段使用过交叉分配法,即各进程交叉去拿数,除了交叉分配法还有分块法和池类算法,然后我们又用池类算法来实现了一次,即使用一个main线程来分发任务,下面的下游线程去取任务,通过中间的num来判断此时的任务状况,当num>0的时候,表示有任务,=0的时候表示暂时没有任务,=-1的时候表示全部结束,退出,示意图如下:

但是其实这个算法的效率依然不是很高,问题出在哪里?int型的那个num空间太小,说白了只要你下游的几个线程的处理时间不是main发任务的时间的n分之一,那就会造成等待,因为上游只有一个人在往下扔任务,而下游有三个人在等待。因此我们的思路就来了,我们把空间放大,上游一次不是仍一个任务,可以一次性扔个10个甚至更多是吧,那这样的话我们可以把这片空间变为类似队列的结构---管道。

那么我们把它变成这种结构:

好,下面开始实现下我们的管道:

mypipe.h

#ifndef MYPIPE_H__
#define MYPIPE_H__

#define PIPESIZE 1024
#define MYPIPE_READER 0x00000001UL
#define MYPIPE_WRITER 0x00000002UL

typedef void mypipe_t;

mypipe_t *mypipe_init(void);

int mypipe_register(mypipe_t *, int opmap);

int mypipe_unregister(mypipe_t *, int opmap);

int mypipe_read(mypipe_t *, void *buf, size_t count);

int mypipe_write(mypipe_t *, const void *buf, size_t count);

int mypipe_destroy(mypipe_t *);

#endif

mypipe.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "mypipe.h"

struct mypipe_st
{
    int head;            // 头
    int tail;            // 尾
    char data[PIPESIZE]; // 数据
    int datasize;        // 有效数据长度
    pthread_mutex_t mut; // 互斥量
    pthread_cond_t cond; // 条件变量
    int count_r;         // 读者数量
    int count_w;         // 写者数量
};

// 初始化管道
mypipe_t *mypipe_init(void)
{
    struct mypipe_st *me;
    me = malloc(sizeof(*me));
    if (me == NULL)
    {
        return NULL;
    }
    // 赋值
    me->head = 0;
    me->tail = 0;
    me->datasize = 0;
    me->count_r = 0;
    me->count_w = 0;
    pthread_mutex_init(&me->mut, NULL);
    pthread_cond_init(&me->cond, NULL);

    return me;
}

// 注册身份
int mypipe_register(mypipe_t *ptr, int opmap)
{
    struct mypipe_st *me = ptr;
    // 加锁
    pthread_mutex_lock(&me->mut);
    // 读者
    if (opmap & MYPIPE_READER)
    {
        me->count_r++;
    }
    // 写者
    if (opmap & MYPIPE_WRITER)
    {
        me->count_w++;
    }
    // 给读者或者写者++之后就可以尝试唤醒
    pthread_cond_broadcast(&me->cond);
    // 写者的数量或者读者的数量必须至少有一边大于0,否则就等待
    while (me->count_r <= 0 || me->count_w <= 0)
    {
        pthread_cond_wait(&me->mut, &me->cond);
    }
    // 解锁
    pthread_mutex_unlock(&me->mut);
    return 0;
}

// 解除身份
int mypipe_unregister(mypipe_t *ptr, int opmap)
{
    struct mypipe_st *me = ptr;
    // 加锁
    pthread_mutex_lock(&me->mut);
    // 读者
    if (opmap & MYPIPE_READER)
    {
        me->count_r--;
    }
    // 写者
    if (opmap & MYPIPE_WRITER)
    {
        me->count_w--;
    }
    // 尝试唤醒
    pthread_cond_broadcast(&me->cond);
    // 解锁
    pthread_mutex_unlock(&me->mut);
    return 0;
}

// next函数,用于head或者tail向后移动
static int next(int hort)
{
    if (hort + 1 == PIPESIZE)
    {
        return 0;
    }
    return hort + 1;
}

// 单字节读取
static int mypipe_readbyte_unlocked(struct mypipe_st *me, char *datap)
{
    if (me->datasize <= 0)
    {
        return -1;
    }
    // 取出数据
    *datap = me->data[me->head];
    // 头部向后移动
    me->head = next(me->head);
    // size--
    me->datasize--;
    return 0;
}

// 读
int mypipe_read(mypipe_t *ptr, void *buf, size_t count)
{
    struct mypipe_st *me = ptr;
    int i;
    // 加锁
    pthread_mutex_lock(&me->mut);
    // 如果没有数据且有写者的时候那就等待被唤醒
    while (me->datasize <= 0 && me->count_w > 0)
    {
        pthread_cond_wait(&me->mut, &me->cond);
    }
    // 要是没有数据且没有写者了,那就退出了
    if (me->datasize <= 0 && me->count_w <= 0)
    {
        // 解锁
        pthread_mutex_unlock(&me->mut);
        // 退出
        return 0;
    }
    // 循环读数据,每次读一个字节
    for (i = 0; i < count; ++i)
    {
        if (mypipe_readbyte_unlocked(me, buf + i) != 0)
        {
            break;
        }
    }
    // 尝试唤醒写者
    pthread_cond_broadcast(&me->cond);
    // 解锁
    pthread_mutex_unlock(&me->mut);

    return i;
}

// 单字节写入
static int mypipe_writebyte_unlocked(struct mypipe_st *me, char *datap)
{
    if (me->datasize >= PIPESIZE)
    {
        return -1;
    }
    // 写入字节
    me->data[me->tail] = *datap;
    // 尾部后移
    me->tail = next(me->tail);
    // size++
    me->datasize++;
    return 0;
}

// 写
int mypipe_write(mypipe_t *ptr, const void *buf, size_t count)
{
    struct mypipe_st *me = ptr;
    int i;
    // 加锁
    pthread_mutex_lock(&me->mut);
    // 如果数据已经装满了并且有读者存在,那就等待唤醒
    while (me->datasize >= PIPESIZE && me->count_r > 0)
    {
        pthread_cond_wait(&me->mut, &me->cond);
    }
    // 如果数据已经装满了并且没有读者了,那就退出了
    if (me->datasize >= PIPESIZE && me->count_r <= 0)
    {
        // 解锁
        pthread_mutex_unlock(&me->mut);
        // 退出
        return 0;
    }
    // 开始循环写数据,每次写一个字节
    for (i = 0; i < count; ++i)
    {
        if (mypipe_writebyte_unlocked(me, buf + i) != 0)
        {
            break;
        }
    }
    // 写完数据后就可以尝试唤醒读者
    pthread_cond_broadcast(&me->cond);
    // 解锁
    pthread_mutex_unlock(&me->mut);

    return i;
}

// 摧毁管道
int mypipe_destroy(mypipe_t *ptr)
{
    struct mypipe_st *me = ptr;
    pthread_mutex_destroy(&me->mut);
    pthread_cond_destroy(&me->cond);
    free(me);
    return 0;
}

其实程序还有其他可以完善的地方大家可以自行完善,比如身份验证:一个人注册了读者身份但是跑去写,这个可以用Linux的fd的实现思路去实现,也就是再封装一层用于权限判断。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值