主要内容:
这个例子可以作为我们完成前面并发、多线程等内容,再到后续的进程间通信的一个过度。为后续我们内核提供的管道机制做铺垫。
回想我们前面写过无数次的例子:筛质数
在之前的筛质数程序中,我们在进程阶段使用过交叉分配法,即各进程交叉去拿数,除了交叉分配法还有分块法和池类算法,然后我们又用池类算法来实现了一次,即使用一个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的实现思路去实现,也就是再封装一层用于权限判断。