Event-based Concurrency (Advanced)

我们前面讨论并发问题都是使用多线程。但是,关于并发的问题不止这一方面,还有使用基于GUI的应用程序和因特网服务器的并发,这些叫做基于事件的并发(event-based concurrency)

要解决的基于事件的并发问题有两个:

  1. 在多线程应用程序中如何正确管理并发性
  2. 在多线程应用程序中开发人员如何控制在给定时刻的调度内容

那么问题来了,(带着问题学习):我们如何在不使用线程的情况下构建并发服务器,从而保持对并发的控制以及避免多线程应用程序的并发问题?

1. The Basic Idea: An Event Loop

基本方法就是题目名event-based concurrency首先等待事件发生;事件发生后,检查事件的类型并做一小部分该事件需要的工作

首先看一个事件循环(event loop)的伪代码:

while (1) 
{
	events = getEvents();
	for (e in events)
		processEvent(e);
}

处理每个事件的代码叫做事件处理程序(event handler)。决定哪个事件被处理是由调度程序(sheduling)决定的。

直接被调度程序控制是基于事件并发方法的优点

但是也留下了更大的问题:基于事件的服务器如何决定哪个事件发生?事件服务器如何判断消息已经到达?

2. An Important API: select() (or poll())

在这里插入图片描述
nfds:描述符集合中从0到nfds – 1的描述符序号
readfds:读状态
writefds:写状态
errorfds:待处理的异常
timeout:超时参数(常设置为ULL,导致select()无限期阻塞,直到准备好一些描述符;功能更强大的服务器通常指定某种超时时间)

函数的返回值为所有集合中就绪描述符的总数。

更多信息需要去查阅手册~

3. Using select()

看一下如何使用select()来查看哪些网络描述符上有传入消息:

// simple code using select()
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

int main(void)
{
    // open and set up a bunch of sockets (not shown)
    // main loop
    while (1)
    {
        // initialize the fd_set to all zero
        fd_set readFDs;
        FD_ZERO(&readFDs);

        // now set the bits for the descriptors
        // this server is interested in
        // (for simplicity, all of them from min to max)
        int fd;
        for (fd = minFD; fd < maxFD; fd++)
            FD_SET(fd, &readFDs);

        // do the select 
        int rc = select(maxFD + 1, &readFDs, NULL, NULL, NULL);

        // check which actually have data using FD_ISSET()
        int fd;
        for (fd = minFD; fd < maxFD; fd++)
            if (FD_ISSET(fd, &readFDs))
                processFD(fd);
    }
}

当然,真实的服务器比上述代码更复杂,而且当发送信息时需要使用逻辑。

基于事件的服务器可对任务计划进行精细控制(fine-grained control)。但是,要保持这种控制,就不能进行阻止调用者执行的调用。

4. Why Simpler? No Locks Needed

在单个CPU和基于事件的应用程序中,多线程并发的一些问题不再存在。因为某一时刻只会有一个事件被处理;不需要获取和释放锁;正在处理的事件也不会被打断因为是单线程。

5. A Problem: Blocking System Calls

基于事件编程听起来非常棒!你只需要写个简单的循环程序就行了。但是,有一个问题:如何某个事件要求您发出可能阻止的系统调用怎么办?

不妨来看一个例子:

客户端(client)向服务器(server)发送一个请求(request):从服务器磁盘读一个文件然后返回文本内容给客户端(类似于HTTP)。

为了服务这个请求,事件处理程序最终需要调用系统函数open()去打开文件,然后调用read()去读这个文件,当文件被读入内存后,服务器开始发送结果给客户端。调用open()和read()可能要向存储系统发出I/O请求(当数据不在内存中时),这可能会使服务器花费相当长的时间。

对于基于线程的服务器(thread-based server)来说,这不是问题:当某线程发出I/O请求后,其他线程可以运行。但是,对于基于事件的服务器(event-based server),由于某一时刻只能一个事件被处理,所以只能等待I/O请求完成,这会花费大量的等待时间,造成资源浪费。

6. A Solution: Asynchronous I/O

为了解决发出I/O请求而导致的问题,很多现代操作系统引进了新的方法叫asynchronous I/O:这些接口使应用程序可以在I/O完成之前发出I/O请求并立即将控制权返回给调用方。其他接口使应用程序可以确定各种I/O是否已经完成。

看一个基于Mac的接口例子:

aiocb means AIO control block

在这里插入图片描述
读操作:

int aio_read(struct aiocb *aiocbp);

该调用尝试发出I/O,如果成功,立即返回,应用程序可以继续工作。

上述代码解决了一部分问题,但还剩一个问题:如何知道何时I/O完成,从而使缓冲区(由aio_buf指向)现在已包含请求的数据?

所以需要如下调用函数:

int aio_error(const struct aiocb *aiocbp);

上述代码返回0表示I/O完成,非0表示错误处理。

上述检查是否成功的调用函数代码很方便,但是如果在给定的时间有很多I/O请求需要处理的话,就需要一个一个的检查,这就很浪费时间了。为了节约时间,一些系统使用中断程序(interrupt):引入信号(signal)表示I/O请求是否完成。

7. Another Problems: State Management

基于事件的方法还有个问题就是要写的程序比基于线程的复杂。理由是:当事件处理程序发出异步I/O(asynchronous I/O),它必须把一些程序状态打包给I/O完成后下一次事件处理程序的使用。

基于线程的方法不需要如此,因为它把状态存入栈中。

对于基于事件的程序,当调用程序告诉我们读操作完成后,服务器如何知道接下来做什么?

解决的方法就是使用旧式编程语言结构continuation:用一些数据结构记录完成处理某事件必要的信息,然后当一些事件发生后,查找必要信息。

8. What Is Still Difficult With Events

基于事件的方法还有一些困难的地方:

  1. 当系统从单CPU变成多CPU后,该方法某些方面的简洁性就消失了。
  2. 它与某些类型的系统活动(如page)无法很好地集成在一起。
  3. 随着各种例程的确切语义发生变化,基于事件的代码可能难以管理超时状态。
  4. 尽管异步I/O现今在多平台都能使用,但是设计非常困难,而且未和异步网络以简单统一的方式集成。

原文地址:http://www.cnblogs.com/astralcon/p/16792725.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性