Windows编程(多线程)

Windows编程(多线程)线程创建函数CreateThreadCreateThread是一种微软在WindowsAPI中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

Windows编程(多线程)

线程创建函数

CreateThread

CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。

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

· 第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

· 第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

· 第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

· 第四个参数 lpParameter 是传给线程函数的参数。

· 第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

· 第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

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

//HANDLE
//WINAPI
//CreateThread(
//    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
//    _In_ SIZE_T dwStackSize,
//    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
//    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
//    _In_ DWORD dwCreationFlags,
//    _Out_opt_ LPDWORD lpThreadId
//);
DWORD WINAPI ThreadFun(LPVOID p) {
	int a = *(int*)p;

	printf("我是子线程,PID = %d,iMym = %d\n", GetCurrentThreadId(), a);
	
	return 0;
}


int main() {
	HANDLE ZThread;
	DWORD dwThreadID;
	int a = 100;
	ZThread = CreateThread(NULL, 0, &ThreadFun, &a, 0, &dwThreadID);
	
}

_beginthreadex

_ACRTIMP uintptr_t __cdecl _beginthreadex(
    _In_opt_  void*                    _Security,
    _In_      unsigned                 _StackSize,
    _In_      _beginthreadex_proc_type _StartAddress,
    _In_opt_  void*                    _ArgList,
    _In_      unsigned                 _InitFlag,
    _Out_opt_ unsigned*                _ThrdAddr
    );
unsigned WINAPI FUNC(VOID * p) {
	int a = *(int*)p;
	printf("我是子线程,PID = %d,iMym = %d\n", GetCurrentThreadId(), a);
	return 0;
}

int main() {
	HANDLE hThread;
	unsigned dwThreadID;
	int a = 1;

	 hThread= (HANDLE)_beginthreadex(NULL, 0, FUNC, (void*)&a,0,&dwThreadID);
	 Sleep(3000);
	
}

1 理解内核对象

​ 1 定义:

内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存,由操作系统内核分配,并且只能由操作系统内核访问。在此数据结构中少数成员如安全描述符和使用计数是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。内核对象的数据结构只能由操作系统提供的API访问,应用程序在内存中不能访问。调用创建内核对象的函数后,该函数会返回一个句柄,它标识了所创建的对象。它可以由进程的任何线程使用。

CreateProcess

CreateThread

CreateFile

event

Job

Mutex

常见的内核对象 : 进程、线程、文件,存取符号对象、事件对象、文件对象、作业对象、互斥对象、管道对象、等待计时器对象,邮件槽对象,信号对象

​ 内核对象:为了管理线程/文件等资源而由操作系统创建的数据块。

​ 其创建的所有者肯定是操作系统。

WaitForSingleObject

等待指定对象处于信号状态或超时间隔结束。

DWORD WaitForSingleObject(
  HANDLE hHandle,//指明一个内核对象的句柄
  DWORD  dwMilliseconds //等待时间
);

hHandle:

对象的句柄。有关可以指定句柄的对象类型的列表,请参阅以下备注部分。

如果此句柄在等待仍处于挂起状态时关闭,则函数的行为未定义。

句柄必须具有SYNCHRONIZE访问权限。有关更多信息,请参阅 标准访问权限

dwMilliseconds:

超时间隔,以毫秒为单位。如果指定了非零值,则函数会等待,直到对象发出信号或间隔结束。如果dwMilliseconds为零,如果对象没有发出信号,函数不会进入等待状态;它总是立即返回。如果dwMillisecondsINFINITE,则该函数将仅在对象收到信号时返回。

int main()
{
	printf("main begin\n");
	int iParam = 5;
	unsigned int dwThreadID;
	DWORD wr;

	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,
		(void*)&iParam, 0, &dwThreadID);

	if (hThread == NULL)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	// 
	printf("WaitForSingleObject begin\n");
	wr = WaitForSingleObject(hThread, INFINITE);
	/*if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
	{
		puts("thread wait error");
		return -1;
	}*/
	printf("WaitForSingleObject end\n");

	printf("main end\n");
	system("pause");
	return 0;
}

线程同步

互斥对象

互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。

互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。

请求互斥对象所有权:调用函数WaitForSingleObject函数。线程必须主动请求共享对象的所有权才能获得所有权。

释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。

CreateMutex
HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全属性
    _In_ BOOL bInitialOwner,   //初始化互斥对象的所有者  TRUE 立即拥有互斥体,false表示创建的这个mutex不属于任何线程;所以处于激发状态,也就是有信号状态
    _In_opt_ LPCWSTR lpName    //指向互斥对象名的指针  L“Bingo”
    );

WaitForMultipleObjects 函数

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

参数

nCount:

lpHandles指向的数组中的对象句柄数。对象句柄的最大数量是MAXIMUM_WAIT_OBJECTS。此参数不能为零。

lpHandles:

对象句柄数组。有关可以指定句柄的对象类型的列表,请参阅以下备注部分。该数组可以包含不同类型对象的句柄。它可能不包含同一句柄的多个副本。

如果这些句柄之一在等待仍然挂起时关闭,则函数的行为是未定义的。

句柄必须具有SYNCHRONIZE访问权限。有关更多信息,请参阅 标准访问权限

bWaitAll:

如果此参数为TRUE,则当lpHandles数组中的所有对象的状态发出信号时,该函数返回。如果为FALSE,则当任何一个对象的状态设置为有信号时,该函数返回。在后一种情况下,返回值指示其状态导致函数返回的对象。

dwMilliseconds:

超时间隔,以毫秒为单位。如果指定了非零值,则函数将等待,直到指定的对象发出信号或间隔过去。如果dwMilliseconds为零,如果指定的对象没有发出信号,函数不会进入等待状态;它总是立即返回。如果dwMillisecondsINFINITE,则该函数将仅在指定对象发出信号时返回。

视窗XP,Windows Server 2003和Windows Vista中,Windows 7和Windows Server 2008和Windows Server 2008 R2dwMilliseconds值不包括在低功耗状态所花费的时间。例如,当计算机处于睡眠状态时,超时确实会继续倒计时。

Windows 8中,Windows Server 2012中的Windows 8.1,Windows Server 2012中R2中,Windows 10和Windows Server 2016dwMilliseconds值不包括在低功耗状态所花费的时间。例如,当计算机处于睡眠状态时,超时不会一直倒计时。

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

HANDLE hMutex;
#define NUM_THREAD	50//线程数
long long num = 0;

unsigned WINAPI ThreadFun1(void* arg)
{
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < 500000; i++)
		num += 1;
	ReleaseMutex(hMutex);

	return 0;
}
unsigned WINAPI ThreadFun2(void* arg)
{
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < 500000; i++)
		num -= 1;
	ReleaseMutex(hMutex);

	return 0;
}

int main()
{
	printf("main begin\n");
	int iParam = 5;

	hMutex = CreateMutexW(NULL, FALSE, 0);
	HANDLE tHandles[NUM_THREAD];

	for (size_t i = 0; i < NUM_THREAD; i++)
	{
		if (i % 2)
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, NULL, 0, NULL);
		else
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, NULL, 0, NULL);
	}

	printf("WaitForSingleObject begin\n");
	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	
	printf("WaitForSingleObject end\n");
	printf("result: %lld \n", num);
	printf("main end\n");
	system("pause");
	return 0;

	
	}

这里建立2个方法,使用50个线程调度。一个方法对num值进行加一,一个方法对num进行减一处理。

来验证结果线程锁是否起作用。

事件对象

事件对象也属于内核对象,它包含以下三个成员:

​ ● 使用计数;

​ ● 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;

​ ● 用于指明该事件处于已通知状态还是未通知状态的布尔值。

​ 事件对象有两种类型:人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;而当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

  1. 创建事件对象

​ 调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。

  1. 设置事件对象状态

​ 调用SetEvent函数把指定的事件对象设置为有信号状态。

  1. 重置事件对象状态

​ 调用ResetEvent函数把指定的事件对象设置为无信号状态。

  1. 请求事件对象

线程通过调用WaitForSingleObject函数请求事件对象。

HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性   
BOOL bManualReset,   // 复位方式  TRUE 必须用ResetEvent手动复原  FALSE 自动还原为无信号状态
BOOL bInitialState,   // 初始状态   TRUE 初始状态为有信号状态  FALSE 无信号状态
LPCTSTR lpName     //对象名称  NULL  无名的事件对象 
);
#include <Windows.h>
#include <stdio.h>
#include <process.h>

#define STR_LEN		100
static char str[STR_LEN];
static HANDLE hEvent;

unsigned WINAPI NumberOfA(void* arg);



unsigned WINAPI NumberOfOthers(void* arg);

unsigned WINAPI NumberOfA(void* arg)
{
	int i, cnt = 0;
	//再没有执行fputs("Input string: ", stdout);
	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
	//WaitForSingleObject
	WaitForSingleObject(hEvent, INFINITE);
	for (i = 0; str[i] != 0; i++)
	{
		if (str[i] == 'A')
			cnt++;
	}
	printf("Num of A: %d \n", cnt);
	return 0;
}
unsigned WINAPI NumberOfOthers(void* arg)
{
	int i, cnt = 0;

	for (i = 0; str[i] != 0; i++)
	{
		if (str[i] != 'A')
			cnt++;
	}
	printf("Num of others: %d \n", cnt - 1);
	//把事件对象设置为有信号状态
	SetEvent(hEvent);
	return 0;
}

int main() {
	HANDLE hThread1, hThread2;
	fputs("Input string: ", stdout);
	fgets(str, STR_LEN, stdin);

	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL,0 ,NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL,0 ,NULL);
	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);
	ResetEvent(hEvent);
	CloseHandle(hEvent);
	return 0;
}

关键代码段

调用InitializeCriticalSection函数初始化一个关键代码段。

InitializeCriticalSection(
    _Out_ LPCRITICAL_SECTION lpCriticalSection
    );

该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SECTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

  1. 初始化关键代码段

​ 调用InitializeCriticalSection函数初始化一个关键代码段。

InitializeCriticalSection(

  _Out_ LPCRITICAL_SECTION** lpCriticalSection

  );

该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SECTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。

进入关键代码段

VOID

WINAPI

EnterCriticalSection(

  _Inout_ LPCRITICAL_SECTION lpCriticalSection
  );

调用EnterCriticalSection函数,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则该函数就返回;否则该函数会一直等待,从而导致线程等待。

  1. 退出关键代码段
VOID

WINAPI

LeaveCriticalSection(

  _Inout_ LPCRITICAL_SECTION lpCriticalSection);

线程使用完临界区所保护的资源之后,需要调用LeaveCriticalSection函数,释放指定的临界区对象的所有权。之后,其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。

删除临界区

WINBASEAPI

VOID

WINAPI

DeleteCriticalSection(

  _Inout_ LPCRITICAL_SECTION lpCriticalSection

  );

当临界区不再需要时,可以调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。

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


int iTickets = 5000;
CRITICAL_SECTION g_cs;

// A窗口     B窗口
DWORD WINAPI SellTicketA(void* lpParam)
{
	while (1)
	{
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
	while (1)
	{
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}

int main()
{
	HANDLE hThreadA, hThreadB;
	InitializeCriticalSection(&g_cs); //初始化关键代码段
	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
	CloseHandle(hThreadA); //1
	CloseHandle(hThreadB); //1
	Sleep(40000);
	DeleteCriticalSection(&g_cs);//删除临界区
	system("pause");

	return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/119855.html原文链接:https://javaforall.net

(0)
上一篇 2021年12月13日 下午12:00
下一篇 2021年12月13日 下午12:00


相关推荐

  • pycharm的python解释器选择_pycharm中配置python解释器

    pycharm的python解释器选择_pycharm中配置python解释器1 准备工作 1 Pycharm 版本为 3 4 或者更高 2 电脑上至少已经安装了一个 Python 解释器 3 如果你希望配置一个远程解释器 则需要服务器的相关支持 2 本地解释器配置配置本地解释器的步骤相对简洁直观 1 单击工具栏中的设置按钮 2 在 Settings Preferences 对话框中选中 ProjectInter 页面 在 ProjectInter 对应的下

    2026年3月27日
    1
  • 如何理解web安全——同源策略

    如何理解web安全——同源策略什么是同源策略 nbsp nbsp 浏览器同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行狡猾 nbsp nbsp nbsp 同源策略是浏览器的一个安全功能 不同源的客户端脚本在没有明确授权的情况下 不能读写对方资源 只有同一个源的脚本赋予 dom 读写 cookie session ajax 等操作的权限 url 由协议 域名 端口和路径组成 如果两个 url 的协议 域名和端口相同 则这两个 url 是同源的 限制来源不用源的

    2026年2月18日
    3
  • webstorm根据eslint保存的时候格式化代码

    webstorm根据eslint保存的时候格式化代码前言:用webstrom把vue项目设置eslint规则,然后保存的时候根据eslint规则格式化当前组件的代码目录:操作步骤:(参考入口)1、点击settings->pluings->搜索eslint->Install安装​​2、安装好之后,找到settings中ESLint,选中,就会进入配置页面,3、到这一步,实际规则已经配置好了,下来就是调用的问题了,搜索keymap,然后把右边的去掉,改成esli,就可以看到FixESLintP…

    2022年6月6日
    61
  • android jsonarray数组转jsonobject异常_Android开发将List转化为JsonArray和JsonObject[通俗易懂]

    android jsonarray数组转jsonobject异常_Android开发将List转化为JsonArray和JsonObject[通俗易懂]释放双眼,带上耳机,听听看~!客户端需要将List转化为JsonArray和JsonObject的方法:首先,List中的Object的属性需要是public:classPerson{publicStringname;publicStringsex;publicintage;}下面假设有ListpersonList=newArrayList();中已经装载好了数据:JSON…

    2022年5月2日
    71
  • this关键字、this关键字应用

    this关键字、this关键字应用一 this 关键字 1 需求 使用 java 类描述一个动物 1 问题分析 存在同名的成员变量与局部变量时 在方法的内部访问的是局部变量 java 采取的是就近原则的机制访问的 2 内存分析 2 this 关键字代表了所属函数的调用者对象 解释 哪个对象调用这个函数 this 就代表哪个对象 3 this 关键字作用 1 如果存在同名成员变量与局部变量时 在方法内部默认是访问

    2026年3月16日
    2
  • ireport使用教程_layout怎么导入图片

    ireport使用教程_layout怎么导入图片ireport插入图片1.在模板上拖一个image组件,设置它的image Expression为变量$P{logo},如图示,属性下面的is lazy勾上。  不然有可能最后页面渲染出来的image的src为nullimage_0_0_0。2.给变量logo的值。  StringbasePath=request.getScheme()+”://”+requ

    2025年10月20日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号