基于WINAPI的CPP进程与线程使用

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

内核对象

  • 物质

内核对象的本质 : 一个数据块,可理解成结构体,由两部分组成,共有( 安全描述符和使用计数)和私有(不同对象各有不同),属于操作系统

安全描述符(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*)&param, 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()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值