内核对象
- 物质
内核对象的本质 : 一个数据块,可理解成结构体,由两部分组成,共有( 安全描述符和使用计数)和私有(不同对象各有不同),属于操作系统
安全描述符(security descriptor,SD) : 指向所有者,其他用户的访问权限
使用计数 : 创建是为1,每次使用加1,关闭时和使用结束后-1,当为0时被操作系统销毁(操作系统管理)
- 运动
创建 : 通过create api来创建并返回其句柄,创建失败返回0
使用 : 通过WINAPI和句柄来使用
销毁 : 使用close api后,由操作系统根据使用计数来销毁
常见的内核对象 : 进程(process)、线程(thread)、文件(file),存取符号对象、事件对象(event)、文件对象、作业对象、互斥对象(mutex)、管道对象、等待计时器对象,邮件槽对象,信号对象
每一个进程都会创建一个句柄表,用来存放使用的句柄
WaitObject
阻塞代码,直到进程完成
//单个
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,//线程句柄
__in DWORD dwMilliseconds);//等待时间 INFINITE代表无限,直到waited
//多个
DWORD WINAPI WaitForMultipleObjects(
__in DWORD nCount,//线程个数
__in const HANDLE *lpHandles,//句柄数组
__in BOOL bWaitAll,//TRUE代表所有都结束,FALSE代表任意一个结束
__in DWORD dwMilliseconds);//等待时间
信号量
用于管理多条线程
用来表示内核对象的状态
组成 : 计数器 + 最大资源计数 + 当前资源计数
signaled 有信号/触发状态,当前有资源
//(安全属性,可用资源个数,总资源个数,信号量名称(不要可以NULL))
HANDLE semOne = CreateSemaphore(NULL, 1, 3, NULL);
WaitForSingleObject(semOne, INFINITE);//消耗一个资源
//将可用中已用的和未使用的进行释放
//(信号量句柄,释放的个数,最大资源数(默认
ReleaseSemaphore(semOne, 2, NULL);
互斥量
防止同个变量同时被多条线程访问,导致数据错乱
这不,8848,跑得快的不一定赢,不摔跟头才是成功
- 结构
CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向安全属性
_In_ BOOL bInitialOwner, //初始化互斥对象的所有者 TRUE 立即拥有互斥体,false表示创建的这个mutex不属于任何线程,即signaled
_In_opt_ LPCWSTR lpName //指向互斥对象名的指针 L“Bingo”
);
- 步骤
CreateMutex创建 -> WaitForSingleObject加锁 -> ReleaseMutex解锁 -> 循环往复
//多个
//利用循环创造相对应的自增自减线程,理论上最后num==0,但实际上却是一个随机数,引出进程第二个问题,速度差异问题
#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;
HANDLE hMutex;
int main(int argc, char *argv[])
{
HANDLE tHandles[NUM_THREAD];
hMutex=CreateMutex(NULL, FALSE, NULL);//互斥量创建
for(int 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);
CloseHandle(hMutex);//互斥量关闭
printf("result: %lld \n", num);
return 0;
}
unsigned WINAPI threadInc(void * arg)
{
WaitForSingleObject(hMutex, INFINITE);//对单个互斥量的
for(int i=0; i<500000; i++)
num+=1;
ReleaseMutex(hMutex);
return 0;
}
unsigned WINAPI threadDes(void * arg)
{
WaitForSingleObject(hMutex, INFINITE);
for(int i=0; i<500000; i++)
num-=1;
ReleaseMutex(hMutex);
return 0;
}
互斥事件
有时我们不需要再waitfor之后改变状态时使用
- 函数原型
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 默认NULL
BOOL bManualReset, // TRUE为manual-reset须用(Re)setEvent手动复原
BOOL bInitialState, // 初始信号状态 TRUE : signaled FALSE反之
LPCTSTR lpName ) //对象名称 NULL 无名的事件对象
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
CloseHandle(hEvent);
//手动复原时
SetEvent(hEvent);//设置为signaled
ResetEvent(hEvent);//设置为non-signaled
进程线程
当我们程序内,需要两个函数同时执行时(如下载与加载),即可开多线程
而多线程有时会存在时序问题,所以需要进行线程同步,由此可能会出现线程死锁
然后就是资源由进程管理,所以不同进程的资源不共享,所以我们需要用到进程通信来实现
- 区别
程序以进程为单位来进行对系统资源的调度,相当于公司
而线程来执行不同的任务,相当于员工
- 下面是进程与线程常见所掌管的资源
| 进程 | 线程 |
|---|---|
| 地址空间(虚拟内存) | 程序计数器 |
| 全局变量 | 寄存器 |
| 打开文件 | 栈 |
| 子进程 | 状态 |
| 即将发生的定时器 | |
| 信号与信号处理程序 | |
| 账户信息 |
线程
tid
因为在速度上 CPU>>内存>>硬盘io
如果程序都是一个执行完了再执行另一个,就会让CPU空闲很久
而进程线程的存在目的就是组织程序,最大化利用cpu
所以,线程本质就是用来管理函数运行的工具
创建线程
- 结构
CreateThread
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)
· 第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
· 第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。
· 第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
· 第四个参数 lpParameter 是传给线程函数的参数。
· 第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,CREATE_SUSPENDED
· 第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号
CreateThread封装成了_beginthreadex便于使用
#include <stdio.h>
#include <windows.h>
#include <process.h>
DWORD WINAPI ThreadFun(LPVOID p);//WINAPI就是stdcall 来自pascal
int main()
{
HANDLE hThread;
DWORD dwThreadID;//用来存放pid
int m = 5;
hThread = CreateThread(NULL, 0, ThreadFun, &m, 0, &dwThreadID);
printf("main thread : PID = %d son thread : %d\n", GetCurrentThreadId(), dwThreadID);
if (hThread == 0)return -1;
else CloseHandle(hThread);
Sleep(10000);//为了让主线程跑完
}
DWORD WINAPI ThreadFun(LPVOID arg)
{
int cnt = *((int*)arg);//取参
printf("son thread : %d\n", GetCurrentThreadId());
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
puts("running thread");
}
return 0;
}
_beginthreadex
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned WINAPI ThreadFun(void* arg);
int main()
{
HANDLE hThread;
int param = 6;
hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadFun, (void*)¶m, 0, 0);
Sleep(10000);
return 0;
}
unsigned WINAPI ThreadFun(void* arg)
{
int cnt = *((int*)arg);
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
puts("running thread");
}
return 0;
}
进程运行于结束
先保证处于非挂起状态,可利用下面两个函数来控制挂起与恢复
SuspendThread(handle)
ResumeThread(handle)
创建时,就运行,即两个信号量,wait一个,close一个
多个线程同时创建时,运行无先后顺序,同时运行时,可以同时访问同个变量,但由于cpu速度远大于内存,就会导致数据错乱
Sleep(1000);//注意留时间让线程跑完
WaitForSingleObject(handle, INFINITE);//这个也可以起到保证线程跑完的作用
CloseHandle(Handle);
线程同步
不同步
如果多线程对相同资源进行访问,就会因为资源竞争,导致两边不能同步
如下面代码,调用了两次函数,输出结果并不是想象中的200000
#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void Func(int idx)
{
for (int i = 0; i < 1000000; i++) n++;
cout << "Thread " << idx << " ended!" << endl;
}
int main()
{
thread thr1(Func, 1);
thread thr2(Func, 2);
thr1.join();
thr2.join();
cout << "End: " << n << endl;
return 0;
}
关键代码段
又名临界区,由windowsAPI提供,处于该段的代码同时只可被一条线程访问
在需要修改公共数据时使用
因为关键代码段很快(因为不需要内核操作),所以比较好用
上面代码可以像下面这样修改
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
int n = 0;
CRITICAL_SECTION g_cs;
void Func(int idx)
{
for (int i = 0; i < 1000000; ++i)
{
EnterCriticalSection(&g_cs);
++n;
LeaveCriticalSection(&g_cs);
}
cout << "Thread " << idx << " ended!" << endl;
}
int main()

本文围绕进程与线程展开,介绍了内核对象的本质、创建、使用和销毁,阐述了线程的创建、同步方法(关键代码段、原子操作)及死锁问题,还提及线程池以减少开销。同时讲解了进程的资源管理、创建和多种通信方式,最后通过多线程群聊项目进行实战演示。
最低0.47元/天 解锁文章
608





