这里继续分析第三篇提到的线程池的消息队列,这个消息队列具体存储什么内容,后续看到内容再讨论,目前的实现存储的内容为void*,所以不影响我们使用。
//基于链表的方式管理队列,size为队列大小,maxlen为队列最大大小,flags用于标记队列类型,即队列是基于共享内存还是堆内存。
swChannel* swChannel_new(size_t size, int maxlen, int flags)
{
assert(size >= maxlen);//判断size有效性
int ret;
void *mem;//指向总内存的指针
//标记为使用共享内存
if (flags & SW_CHAN_SHM)
{
//基于共享内存申请
mem = sw_shm_malloc(size + sizeof(swChannel));
}
else
{
//基于堆内存
mem = sw_malloc(size + sizeof(swChannel));
}
if (mem == NULL)//申请内存失败
{
swWarn("swChannel_create: malloc(%ld) failed.", size);
return NULL;
}
swChannel *object = mem;//object指向申请内存的初始位置,其实从后面的分析可以看出来,object用于管理这块内存空间,也就是这块空间的头部
mem += sizeof(swChannel);//mem指针指向申请内存的初始位置+swChannel的大小
//object对象的初始化,这里总共初始化sizeof(swChannel)的大小
bzero(object, sizeof(swChannel));
object->size = size;//设置object的size属性
object->mem = mem;//设置object的mem属性
object->maxlen = maxlen;//设置object的maxlen属性
object->flag = flags;//设置object的flags属性,具体属性的定义可以在下面看到一些
//锁的初始化,这里的锁用来管理队列的出队入队时的线程同步
if (flags & SW_CHAN_LOCK)
{
//初始化锁,这里锁的初始化和第三篇分析的一样,需要了解的可以在第三篇里面找到相关的分析
if (swMutex_create(&object->lock, 1) < 0)
{
swWarn("mutex init failed.");
return NULL;
}
}
//通知管道初始化,关于后续管道的使用,具体列篇再分析
if (flags & SW_CHAN_NOTIFY)
{
//管道的初始化,下面展开具体分析
ret = swPipeNotify_auto(&object->notify_fd, 1, 1);
if (ret < 0)
{
swWarn("notify_fd init failed.");
return NULL;
}
}
return object;
}
我们继续分析上篇结尾的管道初始化,这里的代码路径在E:\swoole-src-master\include\swoole.h中。
static inline int swPipeNotify_auto(swPipe *p, int blocking, int semaphore)
{
#ifdef HAVE_EVENTFD //判断是否支持eventfd
return swPipeEventfd_create(p, blocking, semaphore, 0);
#else//在不支持eventfd时,则通过linux的pipe做管道
return swPipeBase_create(p, blocking);
#endif
}
我们分析linux传统的管道的创建,基于eventfd的后续再分析。
//创建管道,其中swPipe指向管道的封装对象
int swPipeBase_create(swPipe *p, int blocking)
{
int ret;
//在堆上申请swPipeBase的空间,其中swPipeBase用于封装管道的读写描述符
swPipeBase *object = sw_malloc(sizeof(swPipeBase));
if (object == NULL)//申请失败
{
return -1;
}
//设置swPipe的blocking属性
p->blocking = blocking;
ret = pipe(object->pipes);//初始化linux管道
if (ret < 0)//初始化失败
{
swWarn("pipe() failed. Error: %s[%d]", strerror(errno), errno);
sw_free(object);
return -1;
}
else
{
//管道的读写操作默认是阻塞的,这里设置为非阻塞的
swSetNonBlock(object->pipes[0]);//设置读
swSetNonBlock(object->pipes[1]);//设置写
p->timeout = -1;//IO多路复用时的超时时间
p->object = object;//设置swPipe的object属性,object属性设置为swPipeBase
//下属的回调函数实现比较简单,我只分析swPipe的读的回调函数实现,这里管道的操作也是通过IO复用的方式实现的,这部分展开讨论下。
p->read = swPipeBase_read;//设置swPipe的读回调函数
p->write = swPipeBase_write;//设置swPipe的写回调函数
p->getFd = swPipeBase_getFd;//设置swPipe的获取管道描述的回调函数
p->close = swPipeBase_close;//设置swPipe的关闭的回调函数
}
return 0;
}
//管道读的实现,其中p指向管道,data为存放数据的地址,length为数据长度信息
static int swPipeBase_read(swPipe *p, void *data, int length)
{
swPipeBase *object = p->object;
//判断条件,是否设置了blocking和timeout值,blocking用来表示是否使用IO复用来控制读,timeout用来控制IO多路复用的超时时间
if (p->blocking == 1 && p->timeout > 0)
{
//用IO多路复用来判断是否可读
if (swSocket_wait(object->pipes[0], p->timeout * 1000, SW_EVENT_READ) < 0)
{
return SW_ERR;
}
}
//调用linux API进行读的操作,如果不用IO多路复用来读,在管道没有数据可以读的时候,会阻塞主流程。
return read(object->pipes[0], data, length);
}
接着我们分析swSocket_wait的实现,这个函数在代码E:\swoole-src-master\src\core\Socket.c里面。
//fd管道描述符,timeout_ms用来控制IO多路复用的超时时间,events表示事件集合
int swSocket_wait(int fd, int timeout_ms, int events)
{
struct pollfd event;//这里采用poll来做IO多路复用器
event.fd = fd;//设置需要监控的文件描述符
event.events = 0;
//设置多事件
if (events & SW_EVENT_READ)
{
event.events |= POLLIN;
}
//设置写事件
if (events & SW_EVENT_WRITE)
{
event.events |= POLLOUT;
}
while (1)
{
//linux poll函数的调用
int ret = poll(&event, 1, timeout_ms);
if (ret == 0)//连接关闭或者超时
{
return SW_ERR;
}
else if (ret < 0 && errno != EINTR)//其他错误
{
swWarn("poll() failed. Error: %s[%d]", strerror(errno), errno);
return SW_ERR;
}
else//正常返回,表示有数据可以读
{
return SW_OK;
}
}
return SW_OK;
}