纤程本质上也是线程,是多任务系统的一部分,纤程为一个线程准并行方式调用多个不同函数提供了一种可能,它本身可以作为一种轻量级的线程使用。它与线程在本质上没有区别,它也有上下文环境,纤程的上下文环境也是一组寄存器和调用堆栈。它是比线程更小的调度单位。注意一般我们认为线程是操作系统调用的最小单位,而纤程相比于线程来说更小,但是它是有程序员自己调用,而不由操作系统调用。系统在调度线程的时候会陷入到内核态,线程对象本身也是一种内核对象,而纤程完全是建立在用户层上,它不是内核对象也没有对象的句柄。通过纤程的机制实际就绕开了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字节的内容,然后切换到写纤程,将读到的这些内容写回到磁盘的新文件中完成拷贝,然后切换到读纤程,这样不停的在读纤程和写纤程中进行切换,直到文件拷贝完毕。再切换回主纤程,最后在主纤程中删除读写纤程,将主纤程转化为线程并结束线程。

windows 纤程的更多相关文章

  1. Windows核心编程:第12章 纤程

    Github https://github.com/gongluck/Windows-Core-Program.git //第12章 纤程.cpp: 定义应用程序的入口点. // #include & ...

  2. Windows核心编程 第十二章 纤程

    第1 2章 纤 程 M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的 U N I X服务器应用程序移植到Wi n d o w s中.U N I ...

  3. 第12章 纤程(Fiber)

    12.1 纤程对象的介绍 (1)纤程与线程的比较 比较 线程(Thread) 纤程(Fiber) 实现方式 是个内核对象 在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位. 调度方式 由Mi ...

  4. 基于纤程(Fiber)实现C++异步编程库(一):原理及示例

    纤程(Fiber)和协程(coroutine)是差不多的概念,也叫做用户级线程或者轻线程之类的.Windows系统提供了一组API用户创建和使用纤程,本文中的库就是基于这组API实现的,所以无法跨平台 ...

  5. DELPHI纤程的演示

    DELPHI纤程的演示 DELPHI7编译运行通过. 纤程实现单元: unit FiberFun; //Fiber(纤程测试Demo)//2018/04/11//QQ: 287413288 //参考 ...

  6. 纤程(FIBER)

    Indy 10 还包含对纤程的支持.纤程是什么?简单来说,它也是 一个“线程”,但是它是由代码控制的,而不是由操作系统控制的.实际上,可以认为线程 是一个高级纤程.纤程和 Unix 用户线程(Unix ...

  7. Java 中的纤程库 – Quasar

    来源:鸟窝, colobu.com/2016/07/14/Java-Fiber-Quasar/ 如有好文章投稿,请点击 → 这里了解详情 最近遇到的一个问题大概是微服务架构中经常会遇到的一个问题: 服 ...

  8. nodejs中的fiber(纤程)库详解

    fiber/纤程 在操作系统中,除了进程和线程外,还有一种较少应用的纤程(fiber,也叫协程).纤程常常拿来跟线程做对比,对于操作系统而言,它们都是较轻量级的运行态.通常认为纤程比线程更为轻量,开销 ...

  9. 纤程与Quasar

    Java使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面建立一个新的线程,然鹅新建线程的开销是很大的(每个线程默认情况下会占用1MB的内存空间,当然你 ...

随机推荐

  1. 怎样获取HTML5视频的持续时间

    HTML5视频的Bigger体验是非常令人振奋的,非常easy的道理,不用载入和依赖烦人的Flash或其它第三方插件来播放视频,也是大功一件.我们能够通过自己定义控件对视频进行显示和操控,当中一个常见 ...

  2. Ansible@一个高效的配置管理工具--Ansible configure management--翻译(一)

    未经书面许可,请勿转载 ---      Ansible is the simplest way to automate apps and IT infrastructure 这是Ansible官方站 ...

  3. 一.windows环境下rabbitMQ的的安装和配置

    rabbitMQ是AMQP 0-9-1(高级消息队列协议)的一个实现,使用Erlang语言编写,利用了Erlang的分布式特性.用它来实现分布式消息队列. 1.因为是用Erlang编写的,所以首先要安 ...

  4. Java加密与解密笔记(四) 高级应用

    术语列表: CA:证书颁发认证机构(Certificate Authority) PEM:隐私增强邮件(Privacy Enhanced Mail),是OpenSSL使用的一种密钥文件. PKI:公钥 ...

  5. iOS 视频开发学习

    原文:浅谈iOS视频开发 这段时间对视频开发进行了一些了解,在这里和大家分享一下我自己觉得学习步骤和资料,希望对那些对视频感兴趣的朋友有些帮助. 一.iOS系统自带播放器 要了解iOS视频开发,首先我 ...

  6. 面试题汇总--数据储存/应用程序/UI控件/客户端的安全性与框架处理。。。

    一 数据储存  1.如果后期需要增加数据库中的字段怎么实现,如果不使用 CoreData 呢?编写 SQL 语句来操作原来表中的字段1)增加表字段ALTER TABLE 表名 ADD COLUMN 字 ...

  7. IntelliJ IDEA如何设置头注释,自定义author和date

    下面这张图,保证你一看就会: 下面这个模板,你拿去改一改就行了. /** * @Author: Gosin * @Date: ${DATE} ${TIME} */ 如果觉得上面名字下面的波浪线碍眼,可 ...

  8. KVM 初探

    KVM 是业界最为流行的 Hypervisor,全称是 Kernel-based Virtual Machine.它是作为 Linux kernel 中的一个内核模块而存在,模块名为 kvm.ko,也 ...

  9. HTML5 矩阵变换

    transforms 使用图形上下文对象的transforms方法修改变换矩阵,该方法的定义如下: context.transform(m11,m12,m21,m22,dx,dy); 其中m11,m1 ...

  10. 阅读MDN文档之StylingBoxes(五)

    目录 BoxModelRecap Box properties Overflow Background clip Background origin Outline Advanced box prop ...