1. 程式人生 > >Win32下的非侵入式協程實現

Win32下的非侵入式協程實現

關於協程和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事件完成的超時時間。
  通過這種方式,原來的程式只需要新增初始化和插入業務邏輯的函式,無需改動業務程式碼就可以實現單執行緒的高併發。

相關推薦

Win32侵入實現

關於協程和libco 本專案Github地址   協程實現非同步其實就是用同步的業務邏輯程式碼,但內部卻執行非同步等待並進行排程,既保證的程式碼的可讀性,又能實現非同步的高併發。在Windows程式設計中,往往會有大量阻塞的IO操作,比如這段程式碼:

python 64: 第4、eventlet實現併發

#!/usr/bin/env python # -*- coding: utf-8 -*- from datetime import datetime import eventlet eventlet.monkey_patch(all=True) from eventlet.green

利用AspectJ實現Android端侵入埋點

前言 最近在專案中遇到通過埋點對使用者行為進行收集的需求,由於專案執行在區域網,而且有一些很細化的需求,比較幾種技術方案之後,選擇了通過AspectJ進行埋點。本文主要介紹筆者對學習和使用AspectJ的總結。 AspectJ是什麼 正如面向物件程式設

系統性能監控系列1:使用JAVA動態代理實現侵入的效能測量方法

歡迎關注公眾號: 當我們開發的服務上線後,線上的系統執行狀態(是否正常,效能是否滿足需求)等等就成了架構師和研發工程師關心的問題 。對於系統監控有很多維度,比如:監控CPU,磁碟IO,監控服務請求的響應時間等。相對於這些來說,我今天要給大家分享的是具體的程式碼層次的

Gevent的實現原理

handle 保存 ont expires 了吧 理解 cal easy try 之前之所以看greenlet的代碼實現,主要就是想要看看gevent庫的實現代碼。。。然後知道了gevent的協程是基於greenlet來實現的。。。所以就又先去看了看greenlet的實

python編:函數實現登錄和註冊

name def 函數式編程 數據 txt main函數 else if判斷 ima 不知道大家有沒有一種感覺,我明明學會了,為什麽一寫代碼就不知道從何下筆了。 初學函數編程的小夥伴通常需要檢測一下自己的學習成果,我們現在就寫一道通過函數來實現登錄和註冊的小程序 def

redis 學習筆記3(哨兵模式分布鎖的實現以及全局唯一id的生成)

pin target 實現 sde 命令 記錄 興趣 mage incr redis實現分布式鎖和全局唯一id應該是較為常見的應用. 實現基於redis的setNX,以及incr命令.還是比較簡單的! 搭建環境以及配置好sping整合,做了下測試,有興趣的載下來看看,自己做

轉載:PHP 實現

新的 做出 操作系統 i++ his golang 空間 復雜 conn 轉自:https://newt0n.github.io/2017/02/10/PHP-%E5%8D%8F%E7%A8%8B%E5%8E%9F%E7%90%86/ 實現 PHP 協程需要了解的基本內容。

Linux高性能網絡:系列04-實現之工作原理

內部 coroutine 朋友 null 數據存儲 測試 處理 交流 系列 目錄 Linux高性能網絡:協程系列01-前言 Linux高性能網絡:協程系列02-協程的起源 Linux高性能網絡:協程系列03-協程的案例 Linux高性能網絡:協程系列04-協程實現之工作原

Linux高性能網絡:系列07-實現之定義

www. gin images lee cpu -o events 其他 p s 目錄 Linux高性能網絡:協程系列01-前言 Linux高性能網絡:協程系列02-協程的起源 Linux高性能網絡:協程系列03-協程的案例 Linux高性能網絡:協程系列04-協程實現之

Linux高性能網絡:系列06-實現之切換

type 上下 上下文 函數定義 數據存儲 就是 esp ges linu 目錄 Linux高性能網絡:協程系列01-前言 Linux高性能網絡:協程系列02-協程的起源 Linux高性能網絡:協程系列03-協程的案例 Linux高性能網絡:協程系列04-協程實現之工作原

實現爬蟲的例子主要優勢在於充分利用IO時間去請求其他的url

ret value utf 換工作 發生 url monkey 兩個 利用 # 分別使用urlopen和requests兩個模塊進行演示 # import requests # 需要安裝的 # from urllib.request import urlopen # #

Linux高效能網路:系列08-實現之排程器

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

Linux高效能網路:系列07-實現之定義

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

Linux高效能網路:系列06-實現之切換

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

Linux高效能網路:系列05-實現之原語操作

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

Linux高效能網路:系列04-實現之工作原理

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

python中實現的本質以及兩個封裝模組greenle、gevent

協程 協程,又稱微執行緒,纖程。英文名Coroutine。 協程是啥 協程是python箇中另外一種實現多工的方式,只不過比執行緒更小佔用更小執行單元(理解為需要的資源)。 為啥說它是一個執行單元,因為它自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協程 切換到另一個協程。 只要這個過程中儲存

Python ,gevent(yield阻塞,greenlet),實現多工(有規律的交替協作執行)

實現多工:程序消耗的資源最大,執行緒消耗的資源次之,協程消耗的資源最少(單執行緒)。 gevent實現協程,gevent是通過阻塞程式碼(例如網路延遲等)來自動切換要執行的任務,所以在進行IO密集型程式時(例如爬蟲),使用gevent可以提高效率(有效利用網路延遲的時間去執行其他任務)。 &

lua實現

協程是個很好的東西,它能做的事情與執行緒相似,區別在於:協程是使用者可控的,有API給使用者來暫停和繼續執行,而執行緒由作業系統核心控制;另外,協程也更加輕量級。這樣,在遇到某些可能阻塞的操作時,可以使用暫停協程讓出CPU;而當條件滿足時,可以繼續執行這個協程。目前在網路伺服器領域,使用Lua協程最好的範例就