内核对象
首先介绍内核对象的概念。应用程序在运行过程中会创建各种资源,如进程,线程,文件,互斥量等等。这些资源均由操作系统管理。操作系统管理这些资源的方式就是:记录这些资源的相关信息,会在其内部生成数据块。每种资源需要维护的信息不同,因此每种资源拥有的数据块格式也不同。这类数据块就是“内核对象”。
即使资源的创建请求是在进程内部完成的,但内核对象的所有者并不是该进程,而是内核即操作系统。内核对象的创建,管理,销毁等都是由操作系统完成。
线程的创建
Windows下创建线程有两种方法,函数的原型如下:
uintptr_t _beginthreadex(void *security, unsigned stack_size, unsigned(*start_address)(void *), void *arglist, unsigned initflag, unsigned *thrdaddr);
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
这里只介绍_beginthreadex,因为CreateThread函数调用出的线程在使用C/C++标准函数时并不稳定。
_beginthreadex函数的参数含义:
security:线程安全相关信息,使用默认设置时传递NULL。
stack_size:要分配给线程的栈大小,传递0时生成默认大小的栈。
start_address:传递给线程的main函数信息。
arglist:线程main函数的参数信息。
initflag:用于指定线程创建后的行为,传递0时,线程创建后立即进入可执行状态。
thrdaddr:用于保存线程ID的变量地址值。
函数调用成功时返回线程句柄,失败时返回0。
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
unsigned WINAPI threadFunc(void *arg);
//WINAPI是Windows固有的关键字,它用于指定参数传递方向,分配的栈返回方式等函数调用规定。
//插入它是为了遵守_beginthreadex函数要求的调用规定。
int main()
{
HANDLE hPthread;
unsigned pthreadId;
int param(5);
hPthread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, static_cast<void *>(¶m), 0, &pthreadId);
if (hPthread == 0)
{
cerr << "_beginthreadex() error!\n";
return -1;
}
Sleep(3000);
cout << "end of main.\n";
return 0;
}
unsigned WINAPI threadFunc(void *arg)
{
int param(*(static_cast<int *>(arg)));
cout << param << endl;
return 0;
}
线程句柄和ID的区别:句柄在不同进程中可以重复,ID即使在不同进程中也不能重复。因此,线程ID是在操作系统范围内区分所有线程,线程句柄是在进程内区分该进程拥有的线程。
内核对象的2中状态
内核对象的两种状态:
(1)终止状态,又称signaled状态。
(2)未终止状态,又称non-signaled状态。
Windows提供两个函数WaitForSingleObject和WaitForMultipleObjects,它们的作用是等待参数中指定的对象进入signaled状态。函数原型如下:
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
参数含义:
hHandle:内核对象句柄。
dwMilliseconds:以ms为单位设置超时时间,INFINITE代表无限等待。
对象进入signaled时返回WAIT_OBJECT_0,超时时返回WAIT_TIMEOUT。
DWORD WaitForMultipleObjects(DWORD nCount, HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);
参数含义:
nCount:需验证的内核对象数。
lpHandles:存有内核对象句柄的数组地址值。
bWaitAll:TRUE表示当全部内核对象都变成signaled时返回,FALSE表示只要有一个内核对象编程signaled就返回。
dwMilliseconds:同理。
成功时返回事件信息,失败时返回WAIT_FAILED。
WaitForSingleObject在很多情况下都会用到,如互斥量的上锁等都是调用这个函数,进程阻塞等待线程结束也可以调用这个函数,因为线程终止时,线程内核对象也会进入signaled状态。值得注意的是,有时候WaitForSingleObject在函数返回后,作为参数的内核对象会重置为non-signaled状态(仔细想想,这正是WaitForSingleObject能用来当作互斥量上锁的原因),重不重置取决于传递过来的内核对象。
函数返回时会再次变成non-signaled状态的内核对象称为“auto-reset模式”的内核对象,否则称作“manual-reset模式”的内核对象。互斥量就属于auto-reset模式。
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
unsigned WINAPI threadFunc(void *arg);
int main()
{
HANDLE hThread;
DWORD res;
unsigned threadId;
int param(5);
hThread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, static_cast<void *>(¶m), 0, &threadId);
if (hThread == 0)
{
cout << "_beginthreadex() error!\n";
return -1;
}
if ((res = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) //等待线程终止
{
cout << "thread wait error!\n";
return -1;
}
if (res == WAIT_OBJECT_0) cout << "wait result:signaled.\n";
else cout << "wait result:time-out.\n";
cout << "end of main.\n";
return 0;
}
unsigned WINAPI threadFunc(void *arg)
{
int cnt(*(static_cast<int *>(arg)));
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
cout << "running thread.\n";
}
return 0;
}
用户态和核心态
在Windows操作系统下,进程运行的状态有两种:用户态和核心态。
用户态下,会禁止访问物理设备,而且会限制访问的内存区域。核心态下,并不会有这两种限制。
一个进程的运行过程中,往往少不了这两种状态间的切换。例如,当一个进程调用函数创建一个线程时,创建申请是在用户态中进行的。但对于线程内核对象是由操作系统创建和管理的,所以进程会从用户态切换到核心态,然后进行线程的创建。总的来说当在用户态遇到系统调用或者相应中断时等等,都会切换到核心态。
用户态下的同步方法
用户态下的同步是无需操作系统帮助而在应用程序级别进行的同步方法。因为无需切换到核心态,因此速度会相对快一些,当然伴随的是功能会少一些。
该方法是使用CRITICAL_SECTION(cs)对象,这不是内核对象,这个对象拥有和互斥量差不多的功能。
cs的初始化和销毁函数:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数是cs对象变量的地址值。
cs的上锁和解锁函数:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int NUMTHREAD = 50;
typedef long long ll;
ll num;
CRITICAL_SECTION cs;
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDec(void *arg);
int main()
{
HANDLE hHandles[NUMTHREAD];
InitializeCriticalSection(&cs);
for (int i = 0; i < NUMTHREAD; i++)
{
if (i & 1) hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
else hHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDec, NULL, 0, NULL);
}
WaitForMultipleObjects(NUMTHREAD, hHandles, TRUE, INFINITE);
DeleteCriticalSection(&cs);
cout << "Result:" << num << endl;
return 0;
}
unsigned WINAPI threadInc(void *arg)
{
EnterCriticalSection(&cs);
for (int i = 0; i < 5000; i++)
num += 1;
LeaveCriticalSection(&cs);
return 0;
}
unsigned WINAPI threadDec(void *arg)
{
EnterCriticalSection(&cs);
for (int i = 0; i < 5000; i++)
num -= 1;
LeaveCriticalSection(&cs);
return 0;
}
如果把cs对象的相关语句注释掉,结果就会出错,因为没有保证线程互斥访问num。
核心态的同步方法
互斥量
创建互斥量:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);
参数含义:
lpMutexAttributes:传递安全相关的配置信息,传递NULL时表示使用默认配置。
bInitialOwner:TRUE表示创建的互斥量对象属于调用该函数的进程,且互斥量进入non-signaled状态。FALSE表示不属于任何进程,且进入signaled状态。
lpName:用于命名互斥量对象,传递NULL表示创建无名的互斥量。
成功时候返回创建的互斥量对象的句柄,失败时返回NULL。
销毁句柄函数:
BOOL CloseHandle(HANDLE hObject);
成功时返回TRUE,失败时候返回FALSE。
上锁则调用的是WaitForSingleObject,解锁调用的是ReleaseMutex。一个进程(或线程)调用WaitForSingleObject返回之后,互斥量会再次进入non-signaled状态,因此其他调用WaitForSingleObject的进程(或线程)会阻塞等待,知道该进程调用ReleaseMutex。
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int NUMTHREAD = 50;
typedef long long ll;
ll num;
HANDLE hMutex;
unsigned WINAPI threadInc(void *arg);
unsigned WINAPI threadDec(void *arg);
int main()
{
HANDLE tHandles[NUMTHREAD];
hMutex = CreateMutex(NULL, FALSE, NULL);
for (int i = 0; i < NUMTHREAD; i++)
{
if (i & 1) tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
else tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDec, NULL, 0, NULL);
}
WaitForMultipleObjects(NUMTHREAD, tHandles, TRUE, INFINITE);
CloseHandle(hMutex);
cout << "Result:" << num << endl;
return 0;
}
unsigned WINAPI threadInc(void *arg)
{
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < 5000; i++)
num += 1;
ReleaseMutex(hMutex);
return 0;
}
unsigned WINAPI threadDec(void *arg)
{
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < 5000; i++)
num -= 1;
ReleaseMutex(hMutex);
return 0;
}
信号量
创建信号量函数原型:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaxinumCount, LPCTSTR lpName);
参数含义和互斥量差不多,其中lInitialCount和lMaxinumCount分别表示信号量初始值和最大值,显然,初始值应小于等于最大值。
信号量的p操作调用的WaitForSingleObject,信号量为0时进入non-signaled状态,大于0时进入signaled状态。
v操作调用的ReleaseSemaphore,原型如下:
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
lReleaseCount表示表示信号量进行v操作之后,要增加的值。若增加之后超过最大值,则不增加并返回FALSE。
第三个参数用于保存修改之前值的变量地址,不需要时传递NULL。
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
static HANDLE semOne, semTwo;
static int num;
unsigned WINAPI read(void *arg);
unsigned WINAPI accu(void *arg);
int main()
{
HANDLE hThread1, hThread2;
semOne = CreateSemaphore(NULL, 0, 1, NULL);
semTwo = CreateSemaphore(NULL, 1, 1, NULL);
hThread1 = (HANDLE)_beginthreadex(NULL, 0, read, NULL, 0, NULL);
hThread2 = (HANDLE)_beginthreadex(NULL, 0, accu, NULL, 0, NULL);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
CloseHandle(hThread1);
CloseHandle(hThread2);
return 0;
}
unsigned WINAPI read(void *arg)
{
for (int i = 0; i < 5; i++)
{
cout << "Input num:";
WaitForSingleObject(semTwo, INFINITE);
cin >> num;
ReleaseSemaphore(semOne, 1, NULL);
}
return 0;
}
unsigned WINAPI accu(void *arg)
{
int sum(0);
for (int i = 0; i < 5; i++)
{
WaitForSingleObject(semOne, INFINITE);
sum += num;
ReleaseSemaphore(semTwo, 1, NULL);
}
cout << "Result:" << sum << endl;
return 0;
}
事件
事件同步对象的特点是在创建时,可以自主选择创建auto-reset模式还是manual-reset模式。
事件的创建函数:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
bManualReset为TRUE时创建manual-reset模式的事件对象,为FALSE时创建auto-reset模式的事件对象。
bInitialState为TRUE时创建signaled状态的事件对象,为FALSE时创建non-signaled状态的事件对象。
在manual-reset下WaitForsingleObject返回后,不会自动进入non-signaled状态,因此还需要下列两个函数:
BOOL ResetEvent(HANDLE hEvent); //进入non-signaled状态
BOOL SetEvent(HANDLE hEvent); //进入signaled状态
示例:
#include<iostream>
#include<cstdio>
#include<Windows.h>
#include<process.h>
using namespace std;
const int LEN = 100;
char str[LEN];
HANDLE hEvent;
int cntA, cntOthers;
unsigned WINAPI numberOfA(void *arg);
unsigned WINAPI numberOfOthers(void *arg);
int main()
{
HANDLE hThread1, hThread2;
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建non-signaled状态的manual-reset模式的事件对象
hThread1 = (HANDLE)_beginthreadex(NULL, 0, numberOfA, NULL, 0, NULL);
hThread2 = (HANDLE)_beginthreadex(NULL, 0, numberOfOthers, NULL, 0, NULL);
//因为事件对象为non-signaled状态,所以这两个线程都会阻塞等待
cout << "Input string:";
cin >> str;
SetEvent(hEvent); //设置事件对象为signaled状态,两个线程会同时结束等待继续进行,因为WaitForSingleObject并不会改变事件对象的状态
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
ResetEvent(hEvent);
CloseHandle(hEvent);
cout << "Numbr of A:" << cntA << endl;
cout << "Number of others:" << cntOthers << endl;
return 0;
}
unsigned WINAPI numberOfA(void *arg)
{
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; str[i] != 0; i++)
{
if (str[i] == 'A') cntA++;
}
return 0;
}
unsigned WINAPI numberOfOthers(void *arg)
{
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; str[i] != 0; i++)
{
if (str[i] != 'A') cntOthers++;
}
return 0;
}