参考
- 《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*)¶m, 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*)¶m, 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;
}