win32多线程程序设计(第二章)

目录

第二章 线程的第一次接触

1,产生一个线程

2, 使用多个线程的结果

 3, 核心对象(Kernel Objects)

4,线程结束代码(Exit Code)

6, 结束主线程

 7,错误处理

8,后台打印

 9,成功的秘诀


第二章 线程的第一次接触

1,产生一个线程

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,    //描述施行于这一新线程的 security 属性。NULL表示            
                                             //使用缺省值。            

DWORD dwStackSize,                            //新线程拥有自己的堆栈。0 表示使用缺省大小:1MB。 

LPTHREAD_START_ROUTINE lpStartAddress,        //新线程将开始的起始地址。这是一个函数指针

LPVOID lpParameter,                          //此值将被传送到上述所指定之新线程函数去,作为
                                             //参数  

DWORD dwCreationFlags,                        //允许你产生一个暂时挂起的线程。默认情况是“立
                                              //即开始执行”

LPDWORD lpThreadId );                         //新线程的 ID 会被传回到这里(不要ID就设为NULL)
 
返回值:
如果 CreateThread( ) 成功,传回一个 handle,代表新线程。否则传回一个
FALSE。如果失败,你可以调用 GetLastError( )获知原因。

注意:

第三个函数指针的定义为:DWORD WINAPI ThreadFunc(LPVOID n)

WINAPI 在 WINDEF.H 中被定义为: #define WINAPI __stdcall

2, 使用多个线程的结果

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

DWORD WINAPI ThreadFunc(LPVOID);
void main()
{
	HANDLE hThread; 
	DWORD threadId;
	int i;
	for (int i = 0; i < 5; i++)
	{
		hThread = CreateThread(NULL,
			0,
			ThreadFunc,
			(LPVOID)i,
			0,
			&threadId);
		if(hThread)
			printf("Thread launched %d\n ", i);
	}
	Sleep(2000);
    CloseHandle(hThread);
}

DWORD WINAPI ThreadFunc(LPVOID p)
{
	int i;
	for (i = 0; i < 10; i++)
		printf("%d%d%d%d%d%d%d\n", i, i, i, i, i, i, i);
	return 0;
}

运行结果:(截取一部分,和书中相似) 


Thread launched 0
 0000000
1111111
2222222
3333333
Thread launched 1
 4444444
5555555
6666666
Thread launched 2
 7777777
8888888
Thread launched 3
 9999999
Thread launched 4

由此可知:

1> 多线程程序无法预期

2> 执行次序无法保证(书中的例子:线程 #4 结束于线程 #3 启动之前——甚至即使线程 #3 较早诞生)

3> Task Switches 可能在任何时刻任何地点发生

        书中有一个例子:

        

 像这样的问题是可以解决的,只要你在编译时使用 /MT 或 /MD 选项,表 示要使用多线程版本的 C runtime library(详见第8章)。多线程版函数库 确保你的输出不会像此例这样被中断并混合。

 4> 线程对于小的改变有高度的敏感

#include <Windows.h>
#include<fstream>
#include <tchar.h>
using namespace std;

DWORD WINAPI ThreadFunc(LPVOID);
void main()
{
	HANDLE hThread[5]; 
	DWORD threadId[5];
	FILE* pFile;
	int n;
	TCHAR cName[30];
	//wsprintf(cName, _T("%i"), int(p));
	//lstrcat(cName, _T("_myfile.TXT"));
	_tcscpy_s(cName, _T("myfile.TXT"));
	fopen_s(&pFile, cName, "w");
	for (int i = 0; i < 5; i++)
	{
		hThread[i] = CreateThread(NULL,
			0,
			ThreadFunc,
			(LPVOID)pFile,
			0,
			&threadId[i]);
		if(hThread)
			printf("Thread launched %d\n ", i);
	}
	Sleep(2000);
	int ret = WaitForMultipleObjects(5, hThread, TRUE, INFINITE);
	if (ret = WAIT_OBJECT_0)
	{
		fclose(pFile);
		for (int i = 0; i < 5; i++) 
        {
			CloseHandle(hThread[i]);
		}
    }
}

DWORD WINAPI ThreadFunc(LPVOID p)
{
	FILE* pFile = (FILE *)p;

	for (int i = 0; i < 10; i++)
	{
		fprintf_s(pFile, "%d%d%d%d%d%d\n", i, i, i, i, i, i);
	}

	return 0;
}

运行结果:(截取一部分)

000000
000000
000000
111111
222222
333333

 (书中运行的结果如截图):因为重导至文件的那 些输出远比屏幕上的输出快得多,所以比较有顺序。

 这样的差异告诉我们,程序可能只因为一点小小的不同——甚至不是程序 本身的因素——而结果互异。在抢先式多任务调度中,每个线程被许以一个固 定量的时间片(称为 timeslice),然后控制权就会被转移。由于“屏幕显示” 是个慢动作,线程在一份 timeslice 中所能处理的行数也就比较少。不过,虽 然程序的执行次序有所改变,但每一份工作都还是能够圆满完成。

 3, 核心对象(Kernel Objects)

CreateThread( )传回来的 handle 被称为一个核心对象(kernel object)。

所谓 handle,其实是个指针,指向操作系统内存空间中的某样东西,那东西不允许 你直接取得。你的程序不能够直接取用它,为的是维护系统的完整性与安全性。

下面是各种 Win32 核心对象的清单。本书涵盖 pipes 之外的每一种核心对 象。

i 进程(processes)

i 线程(threads)

i 文件(files)

i 事件(events)

i 信号量(semaphores)

i 互斥器(mutexes)

i 管道(Pipes。分为 named 和 anonymous 两种)

 特点:可以跨进程使用

核心对象可以有一个以上的拥有者,甚至可以跨进程。为了 保持对每一位主人(拥有者)的追踪,核心对象保持了一个引用计数(reference count),以记录有多少 handles 对应到此对象。对象中也记录了哪一个进程 或线程是拥有者。如果你调用 CreateThread( )或是其他会传回 handle 的函 数,引用计数便累加 1。当你调用 CloseHandle( )时,引用计数便递减 1。稍 后你便会看到 CloseHandle 函数。一旦引用计数降至 0,这一核心对象即自动被摧毁。程序员不能选择由进程或线程拥有对象,一切得视对象类型而定

 线程的handle

1)为了安全防护的缘故,你不可能根据一个线程的 ID 而获得其 handle。

2)如果你调用 CreateThread( )或是其他会传回 handle 的函数,引用计数便累加 1。当你调用 CloseHandle( )时,引用计数便递减 1。一旦引用计数降至 0,这一核心对象即自动被摧毁。 

3)你不可以依赖“因线程的结束而清理所有被这一线程产生的核心对象”。许多对象,例如文件,是被进程拥有,而非被线程拥有。在进程结束之前不能够清理它们。

CloseHandle 的重要性:

BOOL CloseHandle (
 HANDLE hObject
);
参数
hObject 代表一个已打开之对象 handle
返回值
如果成功,CloseHandle( )传回 TRUE。如果失败则传回 FALSE,此时你可
以调用 GetLastError( )获知失败原因。 

如果一个进程没有在结束之前针对它所打开的核心对象调用 CloseHandle( ),操作系统会自动把那些对象的引用计数下降 1。

虽然你可以 依赖系统做实体(physical)上的清除(cleanup)工作,然而逻辑上的清除 工作又是完全不同的一回事,特别是如果你有许多个进程的话。因为系统并不知道对象实际代表什么意义,所以它不可能知道解构顺序是否重要。

线程对象与线程的不同 

线程的 handle 是指向“线程核心对象”,而不是指向线程本身。

为什么可以在 不结束线程的 情况下关闭其 handle?

1)closehandle不是关闭线程,只是把引用计数减1

核心对象何时被摧毁?

“线程核心对象”引用到的那个线程也会令核心对象开启。因此,线程对象的默认引用计数是 2。当你调用 CloseHandle( )时,引用计数下降 1,当线程结束时,引用计数再降 1。只有当两件事情都发生了(不管顺序如何)的时候,这个对象才会被真正清除。 

4,线程结束代码(Exit Code)

BOOL GetExitCodeThread(
 HANDLE hThread,
 LPDWORD lpExitCode
);
参数
hThread 由 CreateThread( )传回的线程 handle
lpExitCode 指向一个 DWORD,用以接受结束代码(exit code)
返回值
如果成功,GetExitCodeThread( )传回 TRUE,否则传回 FALSE。如果失败,
你可以调用 GetLastError( )找出原因。如果线程已结束,那么线程的结束代
码会被放在 lpExitCode 参数中带回来。如果线程尚未结束,lpExitCode 带回
来的值是 STILL_ACTIVE。 

5, 结束一个线程

VOID ExitThread(
 DWORD dwExitCode
);
参数
dwExitCode 指定此线程之结束代码
返回值
 没有。此函数从不返回。
这个函数有点像 C runtime library 中的 exit( )函数,因为它可以在任
何时候被调用并且绝不会返回。任何代码若放在此行之下,保证不会被执行。 

这个函数是在ThreadFunc()中使用,dwExitCode可以通过GetExitCodeThread的exitCode得知。

6, 结束主线程

如果线程还在 运行而我的程序结束了,会怎样?

一线程的结束(不论是因为返回或因为调用了 ExitThread( )) 会使得程序中的所有线程都被强迫结束,程序也因此而结束。其他线程没有机 会做清理工作,因而主线程一定要等所有的线程都结束了,再结束。

在主线程中调用 ExitThread( )也可以,那会导致主线程结束而“worker 线程”继续存在。然而这么做会跳过 runtime library 中的清理(cleanup) 函数,因而没有将已开启的文件清理掉。我不建议你这么做。

 7,错误处理

什么是 MTVERIFY?

我将在本书使用一个名为 MTVERIFY 的宏,既适用于 GUI 程序也适用于 console 程序。这个宏内部其实是记录并解释 Win32 GetLastError( )的结果。 如果 Win32 函数失败,MTVERIFY( )会打印出一段简短的文字说明。

网上找了一个代码:

#pragma once
/*
* MtVerify.h
*
* The function PrintError() is marked as __inline so that it can be
* included from one or more C or C++ files without multiple definition
* errors.
* To use the PrintError() in an application, it should be taken out,
* placed in its own source file, and the "__inline" declaration removed
* so the function will be globally available.
* [Modified by thinkhy 10/01/04] ...
* [Modified by thinkhy 10/01/07] Added function Myverify.
*/
#include <tchar.h>
#include <Windows.h>
#pragma comment( lib,   "USER32"   )  
#define MTASSERT(a) _ASSERTE(a)  

#ifdef _DEBUG  
#define MTVERIFY(a) if(!(a)) MyVerify(a,_T(#a))  
#else  
#define MTVERIFY(f)          ((  void  )(f))  
#endif  


__inline  void   PrintError(LPTSTR filename, int   lineno, LPTSTR lpszFunc, DWORD errnum)
{
	LPTSTR lpBuffer;
	TCHAR errbuf[256];
#ifdef _WINDOWS  
	TCHAR modulename[MAX_PATH];
#else    // _WINDOWS  
	DWORD numread;
#endif    // _WINDOWS  

	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
		| FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		errnum,
		LANG_NEUTRAL,
		(LPTSTR)&lpBuffer,  // 这个参数很变态! [Commented by thinkhy 10/01/04]  
		0,
		NULL);

	wsprintf(errbuf, _T("Failed at Line: %d in File: %s \r\n\nFunction: %s \r\n\nReason: %s"),
		lineno, filename, lpszFunc, lpBuffer);
#ifndef _WINDOWS  
	WriteFile(GetStdHandle(STD_ERROR_HANDLE), errbuf, strlen(errbuf), &numread, FALSE);
	Sleep(3000);
#else  
	GetModuleFileName(NULL, modulename, MAX_PATH);
	MessageBox(NULL, errbuf, modulename, MB_ICONWARNING | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND);
#endif  
	// exit(EXIT_FAILURE);  
	return;
}

__inline BOOL MyVerify(BOOL what, LPTSTR lpszFunc)
{
#ifdef _DEBUG  
	if (!what)
		PrintError(_T(__FILE__), __LINE__, lpszFunc, GetLastError());
#endif  
	return   what;
}

8,后台打印

1)The Microsoft Threading Model(微软的多线程模型)

        GUI 线程的定义是:拥有消息队列的线程。任何一个特定窗口的消息总是被产生这一窗口的线程抓到并处理。所有对此窗口的改变也都应该由该线程完成。

        如果 worker 线程也产生了一个窗口,那么就会有一个消息队列随之被产生出来并且附着到此线程身上,于是 worker 线程摇身一变成了 GUI 线程

 9,成功的秘诀

1. 各线程的数据要分离开来,避免使用全局变量。
2. 不要在线程之间共享 GDI 对象。
3. 确定你知道你的线程状态。不要径自结束程序而不等待它们的结束。
4. 让主线程处理用户界面(UI)。 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值