libevent并不是线程安全的,但这不代表libevent不支持多线程模式。

前几天在微博上看到ruanyf发了条微博说到apache和nginx的并发模型,看到评论很多人都说不对

于是自己又查了下,总结一下我所学过的网络库或者网络服务器的并发模型

1、muduo:one loop per thread,主线程注册listen事件,通过某种负载均衡机制(round robin)将连接的事件注册到子线程的Reactor上,据说也是Netty的方案,最近也正好在学netty,刚好可以验证一下。

另外,muduo还提到了一个runInLoop()的功能:如果用户在当前线程调用,则回调functor会同步进行,如果在其他线程调用,则IO线程会被唤醒执行这个functor。这种跨线程调用是如何实现的?因为其他线程很可能阻塞在Reactor上。传统方法是用pipe(后面将提到memcache的多线程),在muduo里面是用eventfde(2),将回调放入线程的任务队列,并发送一个uint64大小的消息来唤醒Reactor。

2、nginx:master + worker的工作模式,ruanyf的微博说是master接受连接分配给worker,事实上不是这样的,master只是通过信号管理worker进程,worker之间通过accept_mutex来决定是否将监听套接字加入到loop中。所谓的one loop per process

3、libevent:这是在网上找的资料,libevent并不是线程安全,但不代表其不支持多线程。memcache的网络部分使用libevent,有一个经典的图描述了其多线程实现:
这里写图片描述

这种消息通知+同步层的机制,通过pipe和一个加锁的任务队列(CQ)实现。与muduo的eventfd效果类似。

一 线程的初始化

1线程对象

在进行事件驱动时,每个线程需建立自己的事件根基。由于libevent未提供线程之间通信的方式,我们采用管道来进行线程的通信。同时为方便主线程分配线程,我们还需保留各个线程的id号。因此我们采用如下结构来保留每个线程的有关信息。

typedef struct {

pthread_t thread_id;        #线程ID

struct event_base *base;   #事件根基

struct event notify_event;

int notify_receive_fd;

int notify_send_fd;

CQ new_conn_queue;    #连接队列

} LIBEVENT_THREAD;

2 初始化线程

我们先介绍如何为每个线程创建自己的事件根基。正如前面介绍的我们需自己建立管道来进行线程通信,因此在线程根基初始化后,我们为为管道的读添加事件对象。注意:在libevent中,线程的初始化需在事件根基初始化之后(即event_init之后)。

static void setup_thread(LIBEVENT_THREAD *me) {

if (! me->base)

me->base = event_init();

event_set(&me->notify_event, me->notify_receive_fd,

EV_READ | EV_PERSIST, thread_libevent_process, me);

event_base_set(me->base, &me->notify_event);

event_add(&me->notify_event, 0) == -1)

cq_init(&me->new_conn_queue);

}

将主线程存储于结构体的第0个对象中,然后为每个线程创建管道并创建事件根基。

threads = malloc(sizeof(LIBEVENT_THREAD) * nthreads);

threads[0].base = main_base;

threads[0].thread_id = pthread_self();

for (i = 0; i < nthreads; i++) {

int fds[2];

pipe(fds)

threads[i].notify_receive_fd = fds[0];

threads[i].notify_send_fd = fds[1];

setup_thread(&threads[i]);

接下来从主线程中创建线程,线程创建成功后激活该线程的事件根基,进入事件循环。

for (i = 1; i < nthreads; i++) {

pthread_t thread;

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_create(&thread, &attr, worker_libevent, &threads[i])

}

/* 激活事件根基.*/

static void *worker_libevent(void *arg) {

LIBEVENT_THREAD *me = arg;

return (void*) event_base_loop(me->base, 0);

}

二 分发连接

现在我们有了多个线程和事件根基,那么我们应该如何将主线程接收到的连接分发给各个线程并将其激活呢?本例子中线程的选择采用最简单的轮询方式。

我们需要对accept_handle函数进行修改。在线程池模型中,我们使用子线程代替主线程来创建缓冲区事件对象,主线程只是对激活选中的线程。我们通过向管道写入一个空字节来激活该管道的读请求。

thread += 1;

item->sfd = sfd

cq_push(&threads[thread].new_conn_queue, item);

write(threads[thread].notify_send_fd, "", 1)

三 处理请求

管道的读请求就绪后,回调函数thread_libevent_process被调用,此时就进入到了线程中,后面所有的调度都将在该线程中进行。

首先从管道中读取1个字节,然后创建事件缓冲区对象来实际处理请求。

static void thread_libevent_process(int fd, short which, void *arg) {

char buf[1];

read(fd, buf, 1)

}

四结束语

到这里我们的程序就可以使用线程池和libevent事件驱动来协同工作了。在实际的服务中,我们通常要将服务作为守护进程来运行,那么在linux中,如何编写守护进程呢?后面在继续学习如何编写linux守护进程。

libevent实现多线程的更多相关文章

  1. Libevent 的多线程操作

    起因是event_base 跨线程add/remove event 导致崩溃或者死循环. 据查:libvent 1.4.x是非线程安全的,要跨线程执行event_add,会有问题.因此传统做法是通过p ...

  2. 使用libevent进行多线程socket编程demo

    最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究. libevent是一个用C语言写的开源的一个库.它对socket编程里的epoll/select等功能 ...

  3. memcached使用libevent 和 多线程模式

    一.libevent的使用 首先我们知道,memcached是使用了iblievet作为网络框架的,而iblievet又是单线程模型的基于linux下epoll事件的异步模型.因此,其基本的思想就是 ...

  4. 基于Libevent的HTTP Server

    简单的Http Server 使用Libevent内置的http相关接口,可以很容易的构建一个Http Server,一个简单的Http Server如下: #include <event2/e ...

  5. libevent源码深度剖析

    原文地址: http://blog.csdn.net/sparkliang/article/details/4957667 第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多 ...

  6. libevent源码剖析

    libevent是一个使用C语言编写的,轻量级的开源高性能网络库,使用者很多,研究者也很多.由于代码简洁,设计思想简明巧妙,因此很适合用来学习,提升自己C语言的能力. libevent有这样显著地几个 ...

  7. libevent学习一

    常见的异步IO存在的问题:   1.使用 fcntl(fd, F_SETFL, O_NONBLOCK);,为什么在处理上效率不好.       a.在没有数据可读写的时候,循环会不停执行,浪费掉大部分 ...

  8. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  9. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

随机推荐

  1. Spring Cloud Gateway过滤器精确控制异常返回(实战,控制http返回码和message字段)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 前文<Spring Cloud Gat ...

  2. 快来使用Portainer让测试环境搭建飞起来吧

    Portainer是Docker的图形化管理工具,提供状态显示面板.应用模板快速部署.容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作).事件日志显示.容器控制台操作.Swarm集群和服 ...

  3. MySQL全面瓦解28:分库分表

    1 为什么要分库分表 物理服务机的CPU.内存.存储设备.连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈.为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库 ...

  4. ubuntu20.04安装EasyConnect兼容性问题解决

    目录 1. 命令行启动EasyConnect 2. 降级pango 3. 重新启动EasyConnect,即可成功启动 Ubuntu20.04安装EasyConnect后无法启动的解决方案 工作使用操 ...

  5. 半主机模式和_MICROLIB 库

    半主机是这么一种机制,它使得在ARM目标上跑的代码,如果主机电脑运行了调试器,那么该代码可以使用该主机电脑的输入输出设备.   这点非常重要,因为开发初期,可能开发者根本不知道该 ARM 器件上有什么 ...

  6. hash_map,map,unordered_map效率

    利用unordered_map代替hash_map 实验环境 操作系统 fedora9 编译器版本 gcc4.3 实验方式 各种map使用插入和查找,比较速度和相关性能 代码 参考代码 下面测试说明了 ...

  7. 16-4SUM

    4.30更新,已经AC 18. 4Sum My Submissions QuestionEditorial Solution Total Accepted: 71102 Total Submissio ...

  8. C#gridview颜色提示

    OnRowCreated="gridStatistic_RowCreated private void FillUI() { gridStatistic.DataSource = dtSta ...

  9. 巩固javaweb的第二十五天

    常用的验证 1. 非空验证 // 验证是否是空 function isNull(str) { if(str.length==0) return true; else return false; } 2 ...

  10. Identity Server 4 从入门到落地(七)—— 控制台客户端

    前面的部分: Identity Server 4 从入门到落地(一)-- 从IdentityServer4.Admin开始 Identity Server 4 从入门到落地(二)-- 理解授权码模式 ...