windows 纤程

纤程本质上也是线程,是多任务系统的一部分,纤程为一个线程准并行方式调用多个不同函数提供了一种可能,它本身可以作为一种轻量级的线程使用。它与线程在本质上没有区别,它也有上下文环境,纤程的上下文环境也是一组寄存器和调用堆栈。它是比线程更小的调度单位。注意一般我们认为线程是操作系统调用的最小单位,而纤程相比于线程来说更小,但是它是有程序员自己调用,而不由操作系统调用。系统在调度线程的时候会陷入到内核态,线程对象本身也是一种内核对象,而纤程完全是建立在用户层上,它不是内核对象也没有对象的句柄。通过纤程的机制实际就绕开了Windows的随机调度线程执行的行为,调度算法由应用程序自己实现,这对一些并行算法非常有意义。因为纤程和线程本质上的类同性,所以也要按照理解线程为函数调用器的方式来理解纤程。

纤程的创建

纤程的创建需要必须建立在线程的基础之上。在线程中调用函数ConvertThreadToFiber可以将一个线程转化为纤程(或者说将一个线程与纤程绑定,以后可以将该纤程看做主纤程)。其他的纤程函数必须在纤程中调用,也就是说,如果目前在线程中,需要调用ConverThreadToFiber将线程转化为纤程,才能调用对应的API。这个函数的原型如下:

LPVOID WINAPI ConvertThreadToFiber(
  LPVOID lpParameter
);

这个函数传入一个参数,类似于CreateThread函数中的线程函数参数,如果我们在主纤程中需要使用到它,可以使用宏GetFiberData取得这个参数。 在调用这个函数创建新纤程后,系统大概会给纤程分配200字节的栈空间,用来执行纤程函数,和保存纤程环境。这个环境由下面几个部分的内容组成: 1. 用户定义的值,这个值就是纤程回调函数中传入的参数 2. 新的结构化异常处理的链表头 3. 纤程内存栈的最高和最低地址,当线程转换为纤程的时候,这也是线程的内存栈。之前说过纤程栈是在建立在线程的基础之上,保留这两个值是为了当纤程还原为线程后,用来还原线程栈环境 4. 各种CPU寄存器环境,相当于线程的CONTENT,但是没有这个结构那么复杂,它只是保存了几个简单的寄存器的值。需要特别注意的一点是,它并没有保存对应浮点数寄存器FPU的值,所以在纤程中使用浮点数计算可能会出现未知错误。如果一定要计算浮点数,那么可以使用ConverThreadToFiberEx,在第二个参数的位置传入FIBER_FLAG_FLOAT_SWITCH值,表示将初始化并保存FPU。 可以在主纤程中调用CreateFiber函数创建子纤程。该函数原型如下:

LPVOID WINAPI CreateFiber(
  DWORD dwStackSize, 
  LPFIBER_START_ROUTINE lpStartAddress, 
  LPVOID lpParameter 
);

第一个参数是纤程的堆栈大小,默认给0的话,它会根据实际需求创建对应大小的堆栈,纤程的堆栈是建立在线程的基础之上,我们可以这样理解,它是从线程的堆栈中隔离一块作为纤程的堆栈。本质上它的堆栈是放在线程的堆栈上。 第二个参数是一个回调,与线程函数类似,这个函数是一个纤程函数。 第三个参数是传递到回调函数中的参数。 函数CreateFiber 和 ConvertThreadToFiber 函数都返回一个void* 的指针,用来唯一标识一个纤程,在这我们可以将它理解为纤程的HANDLE .

纤程的删除

当纤程结束时需要调用DeleteFiber来删除线程,类似于CloseHandle来结束对应的内核对象。如果是调用转化函数由线程转化而来,调用DeleteFiber相当于调用ExitThread来终止线程,所以对于这种情况,最好是将纤程转化为线程,然后再设计一套合理的线程退出机制。

纤程的调度

在任何一个纤程内部调用SwitchToFiber函数,将纤程的void*指针传入,即可切换到对应的纤程,该函数可以在任意几个纤程中进行切换,不管这些纤程是在一个线程中或者在不同的线程中。但是最好不要在不同线程中的纤程中进行切换,它可能会带来意想不到的情况,假设存在这样一种情况,线程A创建纤程FA,线程B创建纤程FB,当我们在系统运行线程A时将纤程从FA切换到FB,由于纤程的堆栈是建立在线程之上的,所以这个时候纤程B仍然使用线程A的堆栈,但是它应该使用的线程B的堆栈,这样可能会对线程A的堆栈造成一定的破坏。 下面是纤使用的一个具体的例子:

#define PRIMARY_FIBER 0
#define WRITE_FIBER 1
#define READ_FIBER 2
#define FIBER_COUNT 3
#define COPY_LENGTH 512

VOID CALLBACK ReadFiber(LPVOID lpParam);
VOID CALLBACK WriteFiber(LPVOID lpParam);

typedef struct _tagFIBER_STRUCT
{
    DWORD dwFiberHandle;
    HANDLE hFile;
    LPVOID lpParam;
}FIBER_STRUCT, *LPFIBER_STRUCT;

char *g_lpBuffer = NULL;
LPVOID g_lpFiber[FIBER_COUNT] = {};
void GetApp(LPTSTR lpPath, int nBufLen)
{
    TCHAR szBuf[MAX_PATH] = _T("");
    GetModuleFileName(NULL, szBuf, MAX_PATH);

    int nLen = _tcslen(szBuf);
    for(int i = nLen; i > 0; i--)
    {
        if(szBuf[i] == '\\')
        {
            szBuf[i + 1] = _T('\0');
            break;
        }
    }

    nLen = _tcslen(szBuf) + 1;
    int nCopyLen = min(nLen, nBufLen);
    StringCchCopy(lpPath, nCopyLen, szBuf);
}

int _tmain(int argc, _TCHAR* argv[])
{
    g_lpBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, COPY_LENGTH);
    FIBER_STRUCT fs[FIBER_COUNT] = {0};

    TCHAR szDestPath[MAX_PATH] = _T("");
    TCHAR szSrcPath[MAX_PATH] = _T("");
    GetApp(szDestPath, MAX_PATH);
    GetApp(szSrcPath, MAX_PATH);
    StringCchCat(szSrcPath, MAX_PATH, _T("2.jpg"));
    StringCchCat(szDestPath, MAX_PATH, _T("2_Cpy.jpg"));

    HANDLE hSrcFile = CreateFile(szSrcPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    HANDLE hDestFile = CreateFile(szDestPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

    fs[PRIMARY_FIBER].hFile = INVALID_HANDLE_VALUE;
    fs[PRIMARY_FIBER].lpParam = NULL;
    fs[PRIMARY_FIBER].dwFiberHandle = 0x00001234;

    fs[WRITE_FIBER].hFile = hDestFile;
    fs[WRITE_FIBER].lpParam = NULL;
    fs[WRITE_FIBER].dwFiberHandle = 0x12345678;

    fs[READ_FIBER].hFile = hSrcFile;
    fs[READ_FIBER].dwFiberHandle = 0x78563412;
    fs[READ_FIBER].lpParam = NULL;

    g_lpFiber[PRIMARY_FIBER] = ConvertThreadToFiber(&fs[PRIMARY_FIBER]);    
    g_lpFiber[READ_FIBER] = CreateFiber(0, (LPFIBER_START_ROUTINE)ReadFiber, &fs[READ_FIBER]);
    g_lpFiber[WRITE_FIBER] = CreateFiber(0, (LPFIBER_START_ROUTINE)WriteFiber, &fs[WRITE_FIBER]);

    //切换到读纤程
    SwitchToFiber(g_lpFiber[READ_FIBER]);

    //删除纤程
    DeleteFiber(g_lpFiber[WRITE_FIBER]);
    DeleteFiber(g_lpFiber[READ_FIBER]);

    CloseHandle(fs[READ_FIBER].hFile);
    CloseHandle(fs[WRITE_FIBER].hFile);

    //变回线程
    ConvertFiberToThread();
    return 0;
}

VOID CALLBACK ReadFiber(LPVOID lpParam)
{
    //拷贝文件
    while (TRUE)
    {
        LPFIBER_STRUCT pFS = (LPFIBER_STRUCT)lpParam;
        printf("切换到[%08x]纤程\n", pFS->dwFiberHandle);
        DWORD dwReadLen = 0;
        ZeroMemory(g_lpBuffer, COPY_LENGTH);
        ReadFile(pFS->hFile, g_lpBuffer, COPY_LENGTH, &dwReadLen, NULL);
        SwitchToFiber(g_lpFiber[WRITE_FIBER]);
        if(dwReadLen < COPY_LENGTH)
        {
            break;
        }
    }

    SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}

VOID CALLBACK WriteFiber(LPVOID lpParam)
{
    while (TRUE)
    {
        LPFIBER_STRUCT pFS = (LPFIBER_STRUCT)lpParam;
        printf("切换到[%08x]纤程\n", pFS->dwFiberHandle);
        DWORD dwWriteLen = 0;
        WriteFile(pFS->hFile, g_lpBuffer, COPY_LENGTH, &dwWriteLen, NULL);
        SwitchToFiber(g_lpFiber[READ_FIBER]);
        if(dwWriteLen < COPY_LENGTH)
        {
            break;
        }
    }

    SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}

上面这段代码中首先将主线程转化为主纤程,然后创建两个纤程,分别用来读文件和写文件,然后保存这三个纤程。并定义了一个结构体用来向各个纤程函数传入对应的参数。在主线程的后面首先切换到读纤程,在读纤程中利用源文件的句柄,读入512字节的内容,然后切换到写纤程,将读到的这些内容写回到磁盘的新文件中完成拷贝,然后切换到读纤程,这样不停的在读纤程和写纤程中进行切换,直到文件拷贝完毕。再切换回主纤程,最后在主纤程中删除读写纤程,将主纤程转化为线程并结束线程。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏玩转JavaEE

MongoDB文档查询操作(一)

上篇文章我们主要介绍了MongoDB的修改操作,本文我们来看看查询操作。 本文是MongoDB系列的第五篇文章,了解前面的文章有助于更好的理解本文: ---- ...

3686
来自专栏码农分享

SQL Server 多表数据增量获取和发布 4

最关键的在于获取捕获表信息(系统表中间_CT结尾的数据)。 根据网上资料查取,找到了获取当前捕获表时间区间范围内数据的方式。 见[SQL Server 多表...

1592
来自专栏我叫刘半仙

原自己手写一个Mybatis框架(简化)

       继上一篇手写SpringMVC之后,我最近趁热打铁,研究了一下Mybatis。MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操...

2K6
来自专栏java架构师

Stream篇(1)

最近在看一个写的很好的博客,为了加深记忆,把自认为重要的东西,一边看,一边记在这里 一、什么是流、字节序列、字节 一条河中有一条鱼游过,这条鱼就是一个字节,这个...

3037
来自专栏一个会写诗的程序员的博客

DuplicateFileException: Duplicate files copied in APK META-INF/LICENSEDuplicateFileException: Duplic

1022
来自专栏Fish

android文件存储

为了输出数据,要把list中存储的写到一个txt文件里,就顺手学了一下 文件存储的方法,说是学,其实又是百度之后复制粘贴。不过学到了一个关于java中的一个知识...

2329
来自专栏Web项目聚集地

手写一个Mybatis框架

在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源...

1082
来自专栏流媒体

resources.arsc解析

示例apk 示例代码 binary view二进制文件查看工具: android 6.0系统源码(网上搜索下载,这里暂不提供资源)

1662
来自专栏IMWeb前端团队

Redux源码解析系列(四)-- combineReducers

本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载 combindeReducer 字面意思就是用来合并reducer的...

2007
来自专栏积累沉淀

干货--Hadoop自定义数据类型和自定义输入输出格式整合项目案例

正文开始前 ,先介绍几个概念 序列化 所谓序列化,是指将结构化对象转化为字节流,以便在网络上传输或写到磁盘进行永久存储。 反序列化 是指将字节流转回到结构化...

6506

扫码关注云+社区

领取腾讯云代金券