网络编程——Windows中的线程

参考

  1. 《TCP/IP网络编程》 尹圣雨

Windows中的线程

内核对象

操作系统创建的资源,如进程、线程、文件、信号量、互斥量等。这些资源的共同点是,都是由Windows操作系统创建并管理的的资源。不同资源的管理方式有差异,操作系统为了以记录相关信息的方式管理各种资源,在其内部生成数据块。由于每种资源需要维护的信息不同,所以每种资源拥有的数据块格式也有差异。这类数据块称为“内核对象”。

内核对象的所有者是内核(操作系统)。内核对象的创建、管理、销毁时机的决定等工作均由操作系统完成

线程

调用main函数的主体是线程。现代的Linux系列、Windows系列及各种规模不等的操作系统都在操作系统级别支持线程。因此,非显式创建线程的程序可描述为,单一线程模型的应用程序;反之,显式创建单独线程的程序可描述为,多线程模型的应用程序

线程的创建

调用CreateThread函数创建线程,操作系统为了管理这些资源将同时创建内核对象。最后返回用于区分内核对象的整数型“句柄”(Handle)。句柄相当于Linux的文件描述符

#include <windows.h>

HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,
	SIZE_T dwStackSize,
	LPTHREAD_START_ROUTINE lpStartAddress,
	LPVOID lpParameter,
	DWORD dwCreationFlags,
	LPDWORD lpThreadId
);

成功时返回线程句柄,失败时返回NULL。其中,lpThreadAttributes为线程安全相关信息,使用默认设置时传递NULL;dwStackSzie为要分配给线程的栈大小,传递0时生成默认大小的栈;lpStartAddress传递线程的main函数信息;lpParameter为调用main函数时传递的参数信息;dwCreationFlags用于指定线程创建后的行为,传递0时,线程创建后立即进入可执行状态;lpThreadId用于保存线程ID的变量地址值

线程的销毁

Windows线程在首次调用的线程main函数返回时销毁,这是最好的方法

创建“使用线程安全标准C函数”的线程

如果线程要调用C/C++标准函数,需要通过如下方法创建线程。因为通过CreateThread函数调用创建出的线程在使用C/C++标准函数时并不稳定

#include <process.h>

uintptr_t _beginthreadex(
	void* security,
	unsigned stack_size,
	unsigned (* start_address)(void*)
	void* arglist,
	unsigned initflag,
	unsigned* thrdaddr
);

成功时返回线程句柄,失败时返回0。与CreateThread函数相比,参数个数及各参数的含义和顺序相同,只是变量名和参数类型不同。

#include <stdio.h>
#include <Windows.h>
#include <process.h>        /* _beginthreadex, _endthreadex */
unsigned WINAPI ThreadFunc(void* arg);                                                   // WINAPI是Windows固有的关键字,它用于指定参数传递方向、分配的栈返回方式等函数调用相关规定

int main(int argc, char* argv[])
{
	HANDLE hThread;
	unsigned threadID;
	int param = 5;

	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);   // HANDLE是整数型,用于保存返回的线程句柄
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);                                                                          // Sleep函数以1/1000秒为单位进入阻塞状态,此时等待3秒钟
	puts("end of main");
	return 0;
}

unsigned WINAPI ThreadFunc(void* arg)                                                     // ThreadFunc作为线程的main函数
{
	int i;
	int cnt = *((int*)arg);
	for (i = 0; i < cnt; i++)
	{
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}

句柄、内核对象和ID间的关系

线程也属于操作系统管理的资源,因此会伴随着内核对象的创建,并为了引用内核对象而返回句柄。

可以通过句柄区分内核对象,通过内核对象可以区分线程。最终,线程句柄称为区分线程的攻击。

句柄的整数值在不同进程中可能出现重复,但线程ID在跨进程范围内不会出现重复。线程ID用于区分操作系统创建的所有线程

查看内核状态(线程状态)

线程内核对象中需要重点关注线程是否已终止。终止状态又称“signaled状态”,未终止状态称为“non-signaled状态”

进程或线程终止时,操作系统会把相应的内核对象改为signaled状态

WaitForSingleObject()

该函数针对单个内核对象验证signaled状态

#include <windows.h>

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT,失败时返回WAIT_FAILED。其中,hHandle为查看状态的内核对象句柄;dwMilliseconds以1/1000秒为单位指定超时,传递INFINITE时函数不会返回,直到内核对象编程signaled状态

该函数由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象称为“auto-reset模式”的内核对象,而不会自动跳转到non-signaled状态的内核对象称为“manual-reset模式”的内核对象

#include <stdio.h>
#include <Windows.h>
#include <process.h>
unsigned WINAPI ThreadFunc(void* arg);

int main(int argc, char* argv[])
{
	HANDLE hThread;
	DWORD wr;
	unsigned threadID;
	int param = 5;

	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
	if (hThread == 0)
	{
		puts("_beginthreadex() error");
		return -1;
	}

	if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
	{
		puts("thread wait error");
		return -1;
	}

	printf("wait result: %s \n", (wr == WAIT_OBJECT_0) ? "signaled" : "time-out");
	puts("end of main");
	return 0;
}

unsigned WINAPI ThreadFunc(void* arg)
{
	int i;
	int cnt = *((int*)arg);
	for (i = 0; i < cnt; i++)
	{
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}
WaitForMultipleObjects()

该函数可以验证多个内核对象状态

#include <windows.h>

DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);

成功时返回事件信息,失败时返回WAIT_FAILED。nCount为需验证的内核对象数;lpHandles存有内核对象句柄的数组地址值;bWaitAll为TRUE时,则所有内核对象全部变为signaled返回,如果为FALSE,则只要有1个验证对象变为signaled就会返回;dwMilliseconds以1/1000秒为单位指定超时,传递INFINITE时函数不会返回,直到内核对象变为signaled状态

下面使用WaitForMultipleObjects演示Windows平台下的临界区问题

#include <stdio.h>
#include <Windows.h>
#include <process.h>

#define NUM_THREAD 50
unsigned WINAPI threadInc(void* arg);
unsigned WINAPI threadDes(void* arg);
long long num = 0;

int main(int argc, char* argv[])
{
	HANDLE tHandles[NUM_THREAD];
	int i;
	printf("sizeof long long: %d \n", sizeof(long long));
	for (i = 0; i < NUM_THREAD; i++)
	{
		if (i % 2)
		{
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		}
		else
		{
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
		}
	}
	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void* arg)
{
	int i;
	for (i = 0; i < 50000000; i++)
	{
		num += 1;
	}
	return 0;
}

unsigned WINAPI threadDes(void* arg)
{
	int i;
	for (i = 0; i < 50000000; i++)
	{
		num -= 1;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值