Win32下的非侵入式协程实现

标签: 协程 高并发 Win32
16人阅读 评论(0) 收藏 举报
分类:

关于协程和libco
本项目Github地址
  协程实现异步其实就是用同步的业务逻辑代码,但内部却执行异步等待并进行调度,既保证的代码的可读性,又能实现异步的高并发。在Windows编程中,往往会有大量阻塞的IO操作,比如这段代码:

    CHAR buf[4096];
    DWORD Size;
    LONG Offset = 0;
    SIZE_T CSize;

    PVOID CompressData;

    HANDLE g = CreateFile(L"F:\\test.iso", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);

    for (int i = 0;i < COUNT;i++) {
        SetFilePointer(g, 4096 * i, &Offset, FILE_BEGIN);
        ReadFile(g, buf, 4096, &Size, NULL);

        COMPRESSOR_HANDLE Com = NULL;
        CompressData = malloc(4096);
        CreateCompressor(COMPRESS_ALGORITHM_LZMS, NULL, &Com);
        if (Com != NULL) {
            Compress(Com, buf, 4096, CompressData, 4096, &CSize);
            CloseCompressor(Com);
        }
        free(CompressData);
    }

    CloseHandle(g);

  这段代码中,会从文件读取一段固定4096字节的数据,并将其压缩。程序在执行时,因为ReadFile是非阻塞IO,压缩前不得不等待ReadFile完成数据的读取,而这段时间就会导致CPU资源的浪费,如果能一边读取文件,一边对已经读取完的文件内容进行压缩,这就会大大提高CPU的利用率。
  后来发明了异步IO,在Windows上的体现就是IO完成端口,在执行IO操作时,会先像内核注册一个IO监听,由内核负责监听IO的完成并将其挂在IO完成端口的完成队列里,这样应用层可以在这时去做其他的事情,等IO完成了,应用层在轮询IO完成队列时就能知晓并执行后序的操作。但这种机制在代码上可读性很差,IO完成的操作和IO发起的操作不在同一个函数中。
  于是现在又重新拾起了协程,在这篇文章中会介绍实现一种非侵入式的协程IO,看看如何在不改变原有同步业务逻辑的情况下实现异步IO。
  首先需要HOOK掉IO函数为自己自定义的函数,这样在用户看来调用的仍然是原来的同步IO函数,而我则会在函数内将其修改为异步IO。
  首先看自定义的函数的第一部分:

/**
 * 自定义的支持协程的ReadFile
 */
BOOL
WINAPI
Coroutine_ReadFile(
    _In_ HANDLE hFile,
    _Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
    _In_ DWORD nNumberOfBytesToRead,
    _Out_opt_ LPDWORD lpNumberOfBytesRead,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
) {

    //判断是不是纤程
    if (!IsThreadAFiber()) {
        return System_ReadFile(hFile,
            lpBuffer,
            nNumberOfBytesToRead,
            lpNumberOfBytesRead,
            lpOverlapped
        );
    }

    ...
    //申请一个Overlapped的上下文
    PCOROUTINE_OVERLAPPED_WARPPER OverlappedWarpper = (PCOROUTINE_OVERLAPPED_WARPPER)malloc(sizeof(COROUTINE_OVERLAPPED_WARPPER));
    if (OverlappedWarpper == NULL) {
        *lpNumberOfBytesRead = 0;
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }
    memset(OverlappedWarpper, 0, sizeof(COROUTINE_OVERLAPPED_WARPPER));

    ...

    Succeed = System_ReadFile(hFile,
        lpBuffer,
        nNumberOfBytesToRead,
        lpNumberOfBytesRead,
        &OverlappedWarpper->Overlapped
    );
    if (Succeed || GetLastError() != ERROR_IO_PENDING) {
        goto EXIT;
    }

    //手动调度纤程
    CoSyncExecute(FALSE);

    ...

  当用户调用ReadFile时,会跳转到Coroutine_ReadFile函数中,这个函数保留其他的参数,但额外添加了一个Overlapped参数,这样这个ReadFile就从同步转为异步了。在最后,调用了CoSyncExecute来对纤程进行调度。其实这个函数会跳转到实现调度的纤程中去调度其他处于等待中的纤程。当这个IO完成时,调度算法会最终调度回该函数的上下文中,纤程就会从CoSyncExecute调用后面继续执行第二部分:

    SetLastError(OverlappedWarpper->ErrorCode);
    if (OverlappedWarpper->ErrorCode != ERROR_SUCCESS) {
        goto EXIT;
    }

    Succeed = TRUE;

EXIT:
    *lpNumberOfBytesRead = OverlappedWarpper->BytesTransfered;
    free(OverlappedWarpper);

    return Succeed;

  在第二部分,获取并设置了错误码,然后返回完成的IO的字节数并返回。这样,在调用ReadFile的代码逻辑看来,这个ReadFile仍然是阻塞IO,但在阻塞过程中,其实执行流已经跳转到其他的纤程中继续执行了,这样可以充分的利用CPU。
  然后可以看看调度协程是如何进行调度的(当然当前的调度方法并没有什么花样,很简单)。

/**
 * 调度协程
 */
VOID
WINAPI CoScheduleRoutine(
    LPVOID lpFiberParameter
) {

    ...

    //从TLS中获取协程实例
    PCOROUTINE_INSTANCE Instance = (PCOROUTINE_INSTANCE)TlsGetValue(0);

    //如果有完成的IO端口事件,优先继续执行
DEAL_COMPLETED_IO:
    while (GetQueuedCompletionStatus(Instance->Iocp, &ByteTransfered, &IoContext, &Overlapped, Timeout)) {

        PCOROUTINE_OVERLAPPED_WARPPER Context = (PCOROUTINE_OVERLAPPED_WARPPER)
            CONTAINING_RECORD(Overlapped, COROUTINE_OVERLAPPED_WARPPER, Overlapped);
        ...

        //这个结构可能在协程执行中被释放了
        Victim = Context->Fiber;
        SwitchToFiber(Victim);

        ...

    }

    //继续执行因为其他原因打断的协程或者新的协程
    if (!Instance->FiberList->empty()) {

        Victim = (PVOID)Instance->FiberList->front();
        Instance->FiberList->pop_front();
        SwitchToFiber(Victim);

        ...

        //如果有协程可执行,那么可能后面还有新的协程等待执行
        Timeout = 0;
    }
    else {

        //如果没有,那么就让完成端口等久一点
        Timeout = 500;
    }

    goto DEAL_COMPLETED_IO;
}

  其实很简单,首先判断有没有IO事件已经完成,如果有,直接调度到对应的纤程继续执行。否则的话,判断有没有普通的纤程,如果有,则调度到普通纤程。如果没有,很可能暂时都没有任何需要执行的了,就加大等待IO事件完成的超时时间。
  通过这种方式,原来的程序只需要添加初始化和插入业务逻辑的函数,无需改动业务代码就可以实现单线程的高并发。

查看评论

对Spring非侵入式的理解

假设大家都想要把用户代码塞到一个框架里。侵入式的做法就是要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。非侵入式则不需要用户代码引入框架代码的信息,从类的编写者角度来看,察觉不到框...
  • xujiangdong1992
  • xujiangdong1992
  • 2017年06月19日 15:22
  • 599

系统性能监控系列1:使用JAVA动态代理实现非侵入式的性能测量方法

当我们开发的服务上线后,线上的系统运行状态(是否正常,性能是否满足需求)等等就成了架构师和研发工程师关心的问题 。对于系统监控有很多维度,比如:监控CPU,磁盘IO,监控服务请求的响应时间等。相对于这...
  • moshenglv
  • moshenglv
  • 2016年10月16日 17:11
  • 659

关于侵入式和非侵入式

拿智能指针举例,shared_ptr,即我们平时使用比较多的智能指针是非侵入式的,boost库中提供了这个指针。   那么什么是侵入式指针呢。下面给出大概的代码。 template class GL...
  • starry_eve
  • starry_eve
  • 2013年11月30日 18:27
  • 1559

非侵入式设计和侵入式设计

非侵入式系介绍DI用语,我得理解是两个组件(类,接口)之间,比较独立,不深入到另一个类内部,哪位大虾能点拨一二?   关于“侵入式”和“非侵入式”设计 有读者讲“侵入式”这一术语无法理解,...
  • joshua1830
  • joshua1830
  • 2014年09月12日 08:41
  • 1379

关于STL容器实现,非侵入式容器+Iterator框架和“侵入式”容器实现的思考

嗯,看Bjarne的《The C++ Programming Language》的时候记的。刚刚又看到,整理到BLOG上吧。 对于Iterator实现的缺点,即虚函数的调用造成的开销,解决方法只有抹去...
  • hanbf
  • hanbf
  • 2007年08月31日 21:13
  • 1209

go语言非侵入式接口

侵入式接口:需要显式地创建一个类去实现一个接口。 非侵入式接口:不需要显式地创建一个类去实现一个接口。 C++侵入式接口: #include enum SEX { MAIL, FEMAIL ...
  • Nick_666
  • Nick_666
  • 2018年01月01日 21:36
  • 64

Spring&nbsp;中侵入式与非侵入式的区别

假设大家都想要把用户代码塞到一个框架里。侵入式的做法就是要求用户代码“知道”框架的代码,表现为用户代码需要继承框架提供的类。非侵入式则不需要用户代码引入框架代码的信息,从类的编写者角度来看,察觉不到框...
  • u011816231
  • u011816231
  • 2016年01月22日 11:30
  • 14422

【转】非侵入式设计 和侵入式设计 意思?

【转】非侵入式设计 和侵入式设计 意思? 非侵入式系介绍DI用语,我得理解是两个组件(类,接口)之间,比较独立,不深入到另一个类内部,哪位大虾能点拨一二? 关于“侵入式”和...
  • baggio7095586
  • baggio7095586
  • 2013年12月18日 15:43
  • 2699

非侵入式Javascript(MVC模型特性应用)

在Web的早期阶段,也就是在jQuery出现以前,在同一个文件中混杂JavaScript代码和HTML标记是非常流行的做法。将JavaScript代码作为某个特性的值放入HTML元素中也是再正常不过的...
  • cuiyh1993
  • cuiyh1993
  • 2015年06月11日 17:42
  • 596

C++侵入式智能指针的实现

简介在现代C++编程中,智能指针给我们在资源管理上带来了很多好处,这里就不多说了。在工作中,我们常常会用智能指针来管理资源,其中最常用的就是引用计数类智能指针了(shared_ptr)。资源共享型的智...
  • jiange_zh
  • jiange_zh
  • 2016年09月12日 13:07
  • 1670
    个人资料
    等级:
    访问量: 1万+
    积分: 300
    排名: 26万+