天天看点

基于visual c++之windows核心编程代码分析(20) 纤程与线程的互相转换

在Windows2000/XP中,纤程(fiber)相当于用户级别的线程或轻进程.纤程由Win32库函数支持,对核心是不可见的.纤程可以通过SwitchToFiber显示至另一合作纤程,以实现合作纤程之间的协同.

纤程包含独立的目态栈,寄存器状态的控制信息.目态控制的纤程转接要求较高的编程经验.由于纤程属于目态对象,一个纤程被封锁意味着所在线程被封锁.应用程序可以通过ConvertThreadToFiber将线程转换为纤程.与线程对比,纤程具有切换速度快的特点.

Microsoft公司给Windows添加了一种纤程,以便能够非常容易地将现有的UNIX服务器应用程序移植到Windows中。UNIX服务器应用程序属于单线程应用程序(由Windows定义),但是它能够为多个客户程序提供服务。换句话说, UNIX应用程序的开发人员已经创建了他们自己的线程结构库,他们能够使用这种线程结构库来仿真纯线程。该线程包能够创建多个堆栈,保存某些C P U寄存器,并且在它们之间进行切换,以便为客户机请求提供服务。

  显然,若要取得最佳的性能,这些UNIX应用程序必须重新设计,仿真的线程库应该用Windows提供的纯线程来替代。然而,这种重新设计需要花费数月甚至更长的时间才能完成,因此许多公司首先将它们现有的UNIX代码移植到Windows中,这样就能够将某些应用软件推向Windows市场。

  使用纤程

  线程是在Windows内核中实现的,纤程是在用户模式下实现的,内核对纤程一无所知,内核会根据我们定义的算法来对纤程进行调度。

  一个线程可以包含一个或多个纤程。

  转化线程为纤程

  使用纤程的第一个步骤是将已有的线程转换为一个纤程。ConvertThreadToFiber这个函数会为纤程的上下文分配内存,这个上下文的构成是:

  # 一个用户自定义的值

  # 结构化异常处理链的头

  # 纤程栈的顶部和底部的内存地址

  # 某些CPU寄存器,其中包括栈指针、指令指针以及其他寄存器

  当我们分配了纤程执行上下文并对其进行初始化之后,还必须将执行上下文的地址与线程关联起来。这样我们就将线程转换成了一个纤程,该纤程在这个线程中执行。

  其实,除非我们打算创建更多的纤程,并让它们在同一个线程中运行,否则没有理由将一个线程转换为纤程。

  CreateFiber:创建一个纤程

  SwitchToFiber:调用一个纤程(同一个线程中,同一时刻只能执行一个纤程)

  DeleteFiber:通常为一个纤程调用,来删除另一个纤程

  GetCurrentFiber:得到当前正在运行的纤程

**************************************/
/* 预定义 */
#define _WIN32_WINNT 0x0501
/* 头文件 */
#include <windows.h>
#include <stdio.h>
/* 函数声明 */
VOID WINAPI ReadFiberFunc( LPVOID lpParameter );
VOID WINAPI WriteFiberFunc( LPVOID lpParameter );
/* 结构定义 */
// 用于向纤程传递参数
// 本实例是使用读、写文件来演示纤程的调度
// 用户可根据实际情况自行定义
typedef struct _FIBERDATASTRUCT
{
	DWORD dwParameter;		// 预留给向纤程传递待定参数
	DWORD dwFiberResultCode;	// GetLastError() 值
	HANDLE hFile;				// 纤程所操作文件的句柄
	DWORD dwBytesProcessed;		// 已经处理了的字节
}FIBERDATASTRUCT, *LPFIBERDATASTRUCT;
/* 常量定义 */
#define RTN_OK 0			// 返回值 成功
#define RTN_USAGE 1			// 返回值 参数不正确
#define RTN_ERROR 2		// 返回值 错误
#define BUFFER_SIZE 32768	// 缓冲区大小
#define FIBER_COUNT 3		// 主纤程、读纤程、写纤程,共三个
#define PRIMARY_FIBER 0		// 主纤程
#define READ_FIBER 1		// 读纤程
#define WRITE_FIBER 2		// 写纤程

LPVOID g_lpFiber[FIBER_COUNT];	// 纤程地址的数组
LPBYTE g_lpBuffer;				// 缓冲区
DWORD g_dwBytesRead;			// 已读的字节

int main( int argc, char *argv[] )
{
	LPFIBERDATASTRUCT fs;
	// 用法说明
	if (argc != 3)
	{
		printf("Usage: %s <SourceFile> <DestinationFile>\n", argv[0]);
		return RTN_USAGE;
	}
	// 分配FIBERDATASTRUCT空间,FIBER_COUNT个
	fs = (LPFIBERDATASTRUCT)HeapAlloc(
		GetProcessHeap(), 0,
		sizeof(FIBERDATASTRUCT) * FIBER_COUNT);
	if (fs == NULL)
	{
		printf("HeapAlloc error! (rc%=lu)\n", GetLastError());
		return RTN_ERROR;
	}
	//	分配读、写缓冲区
	g_lpBuffer = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE);
	if (g_lpBuffer == NULL)
	{
		printf("HeapAlloc error! (rc=%lu)\n", GetLastError());
		return RTN_ERROR;
	}
	// 打开源文件,将句柄赋值给fs结构的hFile成员,使纤程可以使用该句柄
	fs[READ_FIBER].hFile = CreateFile(
		argv[1], GENERIC_READ,
		FILE_SHARE_READ, NULL, OPEN_EXISTING,
		FILE_FLAG_SEQUENTIAL_SCAN, NULL
		);
	if (fs[READ_FIBER].hFile == INVALID_HANDLE_VALUE)
	{
		printf("CreateFile error! (rc=%lu)\n", GetLastError());
		return RTN_ERROR;
	}
	// 打开目标文件
	fs[WRITE_FIBER].hFile = CreateFile(
		argv[2], GENERIC_WRITE,
		0, NULL, CREATE_NEW,
		FILE_FLAG_SEQUENTIAL_SCAN, NULL
		);
	if (fs[WRITE_FIBER].hFile == INVALID_HANDLE_VALUE)
	{
		printf("CreateFile error! (rc=%lu)\n", GetLastError());
		return RTN_ERROR;
	}

	// 将主线程切换为纤程,为主纤程,只有转换为纤程后才可以切换至其他纤程
	g_lpFiber[PRIMARY_FIBER]=ConvertThreadToFiber(&fs[PRIMARY_FIBER]);
	if (g_lpFiber[PRIMARY_FIBER] == NULL)
	{
		printf("ConvertThreadToFiber failed! rc=%lu\n", GetLastError());
		return RTN_ERROR;
	}
	// 主纤程数据
	fs[PRIMARY_FIBER].dwParameter = 0;
	fs[PRIMARY_FIBER].dwFiberResultCode = 0;
	fs[PRIMARY_FIBER].hFile = INVALID_HANDLE_VALUE;
	// 创建读纤程
	g_lpFiber[READ_FIBER]=CreateFiber(0,ReadFiberFunc,&fs[READ_FIBER]);
	if (g_lpFiber[READ_FIBER] == NULL)
	{
		printf("CreateFiber error! (rc=%lu)\n", GetLastError());
		return RTN_ERROR;
	}
	// 将纤程指针作为参数传给纤程,没有实际意义,为了显示相关信息时区别各纤程
	fs[READ_FIBER].dwParameter = (DWORD)g_lpFiber[READ_FIBER];
	// 创建写纤程
	g_lpFiber[WRITE_FIBER]=CreateFiber(0,WriteFiberFunc,&fs[WRITE_FIBER]);

	if (g_lpFiber[WRITE_FIBER] == NULL)
	{
		printf("CreateFiber error! (rc=%lu)\n", GetLastError());
		return RTN_ERROR;
	}
	fs[WRITE_FIBER].dwParameter = (DWORD)g_lpFiber[WRITE_FIBER];
	// 切换到读程序执行
	SwitchToFiber(g_lpFiber[READ_FIBER]);

	// 由读纤程获写纤程切换回主纤程
	// 显示相关信息
	printf("ReadFiber result == %lu Bytes Processed == %lu\n",
		fs[READ_FIBER].dwFiberResultCode, fs[READ_FIBER].dwBytesProcessed);

	printf("WriteFiber result == %lu Bytes Processed == %lu\n",
		fs[WRITE_FIBER].dwFiberResultCode, fs[WRITE_FIBER].dwBytesProcessed);
	// 删除读写纤程
	DeleteFiber(g_lpFiber[READ_FIBER]);
	DeleteFiber(g_lpFiber[WRITE_FIBER]);
	// 关闭文件句柄、释放内存、返回
	CloseHandle(fs[READ_FIBER].hFile);
	CloseHandle(fs[WRITE_FIBER].hFile);
	HeapFree(GetProcessHeap(), 0, g_lpBuffer);
	HeapFree(GetProcessHeap(), 0, fs);
	return RTN_OK;
}

VOID WINAPI ReadFiberFunc( LPVOID lpParameter )
{
	LPFIBERDATASTRUCT fds = (LPFIBERDATASTRUCT)lpParameter;

	// 判断参数
	if (fds == NULL)
	{
		printf("Passed NULL fiber data. Exiting current thread.\n");
		return;
	}
	// 显示纤程信息
	printf("Read Fiber (dwParameter == 0x%lx)\n", fds->dwParameter);
	// 初始化处理的字节数为0
	fds->dwBytesProcessed = 0;
	// 循环读
	while (1)
	{
		if (!ReadFile(fds->hFile, g_lpBuffer, BUFFER_SIZE,
			&g_dwBytesRead, NULL))
		{
			break;
		}
		// 判断文件是否已经读完
		if (g_dwBytesRead == 0) break;

		// 已经处理的字节,累加
		fds->dwBytesProcessed += g_dwBytesRead;

		// 读一次后切换到写纤程,将读出的数据写入到目标文件
		printf("Switch To Write");
		SwitchToFiber(g_lpFiber[WRITE_FIBER]);
	}
	// 读操作完成,准备交出执行,返回到主纤程中
	fds->dwFiberResultCode = GetLastError();
	SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}

VOID WINAPI WriteFiberFunc( LPVOID lpParameter )
{
	LPFIBERDATASTRUCT fds = (LPFIBERDATASTRUCT)lpParameter;
	DWORD dwBytesWritten;

	// 判断参数
	if (fds == NULL)
	{
		printf("Passed NULL fiber data. Exiting current thread.\n");
		return;
	}
	// 显示纤程信息
	printf("Write Fiber (dwParameter == 0x%lx)\n", fds->dwParameter);
	// 初始化,注意和读纤程的不同
	fds->dwBytesProcessed = 0;
	fds->dwFiberResultCode = ERROR_SUCCESS;

	while (1)
	{
		// 写入数据
		if (!WriteFile(fds->hFile, g_lpBuffer, g_dwBytesRead,
			&dwBytesWritten, NULL))
		{
			// 如果发生错误,退出循环
			break;
		}
		fds->dwBytesProcessed += dwBytesWritten;

		// 写入完成,切换到读纤程
		printf("Switch To Write");
		SwitchToFiber(g_lpFiber[READ_FIBER]);
	}
	// 出错,切换到主纤程
	// 如果写操作不出错,是不可能由写纤程切换回主纤程的
	fds->dwFiberResultCode = GetLastError();
	SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}