【Windows线程开发】Windows线程同步技术

这篇具有很好参考价值的文章主要介绍了【Windows线程开发】Windows线程同步技术。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们在上一章节中讲解了关于Windows的线程基础,相信大家已经对线程有了基本的概念。这一章节中,我们来讲讲线程同步技术,包括加锁技术(原子锁和互斥体)和事件,信号量。

一.原子锁

原子锁主要解决的问题是多线程在操作符方面的问题。

  • 相关问题:

    多个线程对同一个数据进行原子操作时,会产生结果丢失,比如++运算符

我们来写一段代码看看多线程在操作同一个数据的时候出现的问题:

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

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
int g_value = 0;

int main() {
	DWORD nID = 0;
	HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	printf("%d\n", g_value);
	return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (int i = 0; i < 100000000; i++) {
		g_value++;
	}
	return 0;
}


DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (int i = 0; i < 100000000; i++) {
		g_value++;
	}
	return 0;
}
  • 代码解释
    我们创建两个线程,同时对全局变量g_value进行自增操作,两个线程分别自增100000000次,那么最后结果就应该是200000000,我们来看看执行结果:
    【Windows线程开发】Windows线程同步技术
    我们发现,最后结果并不是200000000,那么是为什么呢?

我们来分析一下错误:
当线程A执行g_value++时,如果线程切换正好是在线程A将结果保存到g_value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会继续上一步的操作,继续将值保存到g_value中,线程B的计算结果被覆盖

通俗点来说,就是线程A计算好了g_value的结果,但是还没有保存到g_value,这时候线程切换到了B线程,线程B完成了计算,并且成功保存,当返回到A线程的时候,A线程会继续上一步的保存操作,那么B线程的计算结果就被覆盖掉了。

那么如何来解决这样的问题呢?那就要用到我们的线程同步技术—原子锁了:

  • 原子锁函数:
    InterlockedIncrement()
    InterlockedDecrement()
    InterlockedCompareExcahnge()
    InterlockedExchange()
    我们在上文中提到,原子锁主要针对的是运算符的问题,每一种运算符都有原子锁函数
    我们来看看使用效果:这里以++运算符为例:
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;

int main() {
	DWORD nID = 0;
	HANDLE hThread[2] = { 0 };
	hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	printf("%d\n", g_value);
	return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (int i = 0; i < 100000000; i++) {
		InterlockedIncrement(&g_value);
	}
	return 0;
}


DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (int i = 0; i < 100000000; i++) {
		InterlockedIncrement(&g_value);
	}
	return 0;
}

我们来看看执行效果:
【Windows线程开发】Windows线程同步技术
我们可以发现,当我们使用原子锁的时候,两个线程操作同一个数据,就不会出现结果丢失的问题了。但是我们也不难发现,执行结果慢了很多,这是因为执行过程中多了很多等待事件,这个等待我们在互斥中会讲到。
原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间,只能有一个线程访问

二.互斥体

  • 相关问题
    跟原子锁一样,都是解决多线程下资源的共享使用,但是与原子锁不同的是,互斥体解决的是代码资源的共享使用。
  • 互斥体的使用:
      1. 创建互斥体:
        使用CreateMutex函数:
        MSDN官方文档解释
HMODLE CreateMutex(
	LPSECURITY_ATTRIBUTES lpMutexAttributes,    //安全性
	BOOL bInitialOwner,        //初始拥有者
	LPCSTR lpName              //为互斥命名
);

参数bInitialOwner介绍:
如果此值为 TRUE ,并且调用方创建了互斥体,则调用线程获取互斥体对象的初始所有权。 否则,调用线程不会获取互斥体的所有权。

互斥体特性介绍:

  1. 在任何一个时间点上,只能由一个线程拥有互斥体
  2. 当前任何一个线程不拥有互斥体是,互斥体句柄有信号
  3. 谁先等候互斥体,谁先获取
    1. 等候互斥体:
      上一篇介绍过了,使用等候句柄函数。WaitFor...
    1. 释放互斥体
BOOL ReleaseMutex(
	HANDLE hMutex           //handle of Mutex
);
    1. 关闭互斥体
      使用CloseHandle函数
      我们来看看使用互斥体来解决我们在多线程中遇到的问题:
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hMutex = NULL; //用于接收互斥体句柄

int main() {
	DWORD nID = 0;
	HANDLE hThread[2] = { 0 };
	//hMutex = CreateMutex(NULL, FALSE, NULL);
	hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	printf("%d\n", g_value);
	CloseHandle(hMutex);
	return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	char a[] = "********";
	
	while (1) {
		//WaitForSingleObject(hMutex, INFINITE);
		for (int i = 0; i < strlen(a); i++) {
			printf("%c", a[i]);
			Sleep(125);
		}
		printf("\n");
		//ReleaseMutex(hMutex);
	}
	return 0;
}


DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	char b[] = "--------";
	while (1) {
		//WaitForSingleObject(hMutex, INFINITE);
		for (int i = 0; i < strlen(b); i++) {
			printf("%c", b[i]);
			Sleep(125);
		}
		//ReleaseMutex(hMutex);
		printf("\n");
	}
	return 0;
}

我们来看看不适用互斥体技术的时候的输出:
【Windows线程开发】Windows线程同步技术
我们再来看看使用了互斥体之后:

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

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hMutex = NULL; //用于接收互斥体句柄

int main() {
	DWORD nID = 0;
	HANDLE hThread[2] = { 0 };
	hMutex = CreateMutex(NULL, FALSE, NULL);
	hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	printf("%d\n", g_value);
	CloseHandle(hMutex);
	return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	char a[] = "********";
	
	while (1) {
		WaitForSingleObject(hMutex, INFINITE);
		for (int i = 0; i < strlen(a); i++) {
			printf("%c", a[i]);
			Sleep(125);
		}
		printf("\n");
		ReleaseMutex(hMutex);
	}
	return 0;
}


DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	char b[] = "--------";
	while (1) {
		WaitForSingleObject(hMutex, INFINITE);
		for (int i = 0; i < strlen(b); i++) {
			printf("%c", b[i]);
			Sleep(125);
		}
		ReleaseMutex(hMutex);
		printf("\n");
	}
	return 0;
}

【Windows线程开发】Windows线程同步技术
我们可以发现使用互斥体之后,对代码段进行了枷锁。
我们来大致讲解一下互斥体的实现吧:

我们在主进程中创建了互斥体,并且互斥体不归之进程所有,两个线程谁先等待互斥体句柄,谁就拥有了互斥体,那么当线程跳转到另一个线程之后,发现被锁定在了另一个线程,那么线程就会被阻塞,直到线程再次跳转到另一个线程,执行完之后,互斥体被释放,这时候跳转到这个线程,在这个线程中再进行加锁,这个线程执行完之后,再锁定到另一个线程,这样就实现了加锁技术。

三.事件

前两个技术都属于加锁技术,即两个线程互斥的时候使用,那么线程也会有协调工作的时候,这时候就需要用到我们的事件和信号量了。

  • 相关问题:
    多线程协调工作的时候的通知问题

  • 事件的使用

    • 创建事件:
      使用CreatEvent函数,MSDN官方解释
HANDLE CreateEvent(
	LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性
	BOOL                  bManualReset,   //事件重置/复位方式
	BOOL                  bInitialState, //事件初始状态
	LPCSTR                lpName  //为事件命名
);
  • 参数解释:
  • bManualReset为事件重置/复位方式,如果该参数被设置为TRUE那么就需要我们来手动重置事件对象,如果该参数被设置为FALSE,那么操纵系统会帮我们完成事件的重置和复位。
  • bInitialState:该参数指定了当创建事件后,该事件句柄是否处于有消息状态
  • 等候事件:
    WaitFor......函数
  • 触发事件(使事件句柄处于有消息状态)
BOOLSetEvent(
	HANDLE hEvent
);
  • 复位事件(将事件句柄设置为无消息状态)
BOOL ResetEvent(
	HANDLE hEvent
);
  • 关闭事件
    CloseHandle函数
    我们来看看事件的使用:
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
DWORD g_value = 0;
HANDLE hEvent = NULL; //用于接收事件句柄

int main() {
	DWORD nID = 0;
	HANDLE hThread[2] = { 0 };
	hEvent = CreateEvent(NULL,FALSE,0,NULL);
	hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &nID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	printf("%d\n", g_value);
	CloseHandle(hEvent);
	return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	char a[] = "********";
	while (1) {
		WaitForSingleObject(hEvent, INFINITE);
		for (int i = 0; i < strlen(a); i++) {
			printf("%c", a[i]);
		}
		printf("\n");
		ResetEvent(hEvent);
	}
	return 0;
}


DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	while (1) {
		Sleep(1000);
		SetEvent(hEvent);
	}
	return 0;
}

这是一个很典型的相互协调工作的双线程,我们在A线程中没有设定时间间隔,但是在B线程中设定了事件间隔,我们能够很明显地感受到输出是有时间间隔的:
【Windows线程开发】Windows线程同步技术

四.信号量

  • 相关问题:
    类似于事件,解决线程之间通知的相关问题,但提供一个计数器,可以设置次数。
  • 创建信号量:
    使用CreateSemaphore函数:
    MSDN官方解释
HANDLE CreateSemaphore(
	LPSECURITY_ATTRIBUIES lpSemaphoreAttributes,         //安全属性
	LONG lInitialCount,        //初始化信号量数量
	LONG lMaximumCount,        //信号量的最大值
	LPSTSTR lpName             //为信号量命名
);创建成功返回信号量句柄
  • 等候信号量
    使用WaitFor...函数
    注意: 等候每通过一次,信号量的信号减一,知道为0阻塞
  • 给信号量指定定计数值
    使用ReleaseSemaphore()函数
    MSDN官方解释
BOOL ReleaseSemaphore(
	HANDLE hSeamephore,           //信号量句柄
	LONG lReleaseSemaphore,       //信号量将增加的量
	LPONG lpPreviousCount         //指向一个变量的指针,用于记录信号量的上一个计数
);
  • 关闭信号量:
    使用CloseHandle()函数
    我们来看看信号量的使用实例:
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadProc1(LPVOID lpParameter);
DWORD WINAPI ThreadProc2(LPVOID lpParameter);
HANDLE hSemaphore = NULL; //用于接收事件句柄

int main() {
	DWORD nID = 0;
	HANDLE hThread[2] = { 0 };
	hSemaphore = CreateSemaphore(NULL, 3, 10, NULL);
	hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, &nID);
	while (getchar()=='\n') {
		ReleaseSemaphore(hSemaphore, 5, NULL);
	}
	CloseHandle(hSemaphore);
	return 0;
}

	DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
		char a[] = "********";
		while (1) {
			WaitForSingleObject(hSemaphore, INFINITE);
			for (int i = 0; i < strlen(a); i++) {
				printf("%c", a[i]);
			}
			printf("\n");
		}
		return 0;
	}

我们设置了计数器为3的信号量,我们发现程序最开始只会输出三行,每当我们按一次回车键,就将信号量计数值值为5:
【Windows线程开发】Windows线程同步技术
本篇文章的分享就到这里,如果大家发现有错误之处,还请大家指出来,我会非常虚心地学习。希望我们共同进步!!!文章来源地址https://www.toymoban.com/news/detail-443583.html

到了这里,关于【Windows线程开发】Windows线程同步技术的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • windows C++多线程同步<3>-互斥量

    概念,如下图: 另外就是互斥对象谁拥有,谁释放 那么一个线程允许多次获取互斥对象吗? 答案是允许,但是申请多次就要释放多次,否则其他线程获取不到互斥对象;同一个线程可以多次获取,即使不释放也可以,但是这种做法有问题极易导致线程同步出现异常,强烈不

    2024年02月14日
    浏览(75)
  • 【Windows线程开发】线程基础

    本篇文章来带领大家了解Windows线程,了解线程的基本概念,了解线程的创建方式,以及一些简单的线程操作。 Windows线程是可以执行的代码的实例,系统是以线程为单位调度程序。一个程序中可以有多个线程,实现多任务的处理。 Windows线程的特点: 每个线程都具有一个ID 每

    2024年02月04日
    浏览(41)
  • 【深入浅出C#】章节 9: C#高级主题:多线程编程和并发处理

    多线程编程和并发处理的重要性和背景 在计算机科学领域,多线程编程和并发处理是一种关键技术,旨在充分利用现代计算机系统中的多核处理器和多任务能力。随着计算机硬件的发展,单一的中央处理单元(CPU)已经不再是主流,取而代之的是多核处理器,这使得同时执行

    2024年02月11日
    浏览(56)
  • 要将基于AIGC的技术与低代码平台相结合以提高开发效率,我们可以采取以下几个步骤

    1. 集成Chat-GPT API:首先,需要将Chat-GPT API与低代码平台集成,使用户能够从平台直接与AI进行交互。这将使得Chat-GPT能够理解用户的需求,并为他们提供相应的编程建议和解决方案。 2. 自定义预训练模型:根据低代码平台的特点和目标用户群体,对Chat-GPT模型进行微调,使其能

    2024年02月16日
    浏览(51)
  • 《云计算技术与应用》最新章节测试答案

    云计算是对 并行计算、网格计算、分布式计算 技术的发展与运用 从研究现状上看,下面不属于云计算特点 私有化 超大规模、虚拟化、高可靠性 与网络计算相比,不属于云计算特征的是 适合紧耦合科学计算 亚马逊AWS提供的云计算服务类型是 IaaS、PaaS、SaaS 微软于2008年10月推

    2024年02月02日
    浏览(50)
  • 被我们忽略的HttpSession线程安全问题

    1. 背景 最近在读《Java concurrency in practice》(Java并发实战),其中1.4节提到了Java web的线程安全问题时有如下一段话: Servlet, JSP, Servlet filter 以及保存在 ServletContext、HttpSession 中的对象必须是线程安全的。含义有两点: 1) Servlet, JSP, Servlet filter 必须是线程安全的(JSP的本质其实就

    2024年02月04日
    浏览(37)
  • uniapp开发小程序返回上一页

    小程序导航栏使用 uniapp组件   导航里的属性参数去官网查看uni-nav-bar组件 @clickLeft 左侧按钮点击时触发  触发back()方法来处理返回页 getCurrentPages()  函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。数组长度减

    2024年02月12日
    浏览(58)
  • 最新消息:谷歌将在Chromebook上运用UWB技术,无线通信更上一层

    超宽带(UWB)技术是一种创新的短距离无线通信技术,具有高速数据传输和精确定位物体位置的优势。尽管该技术已经存在一段时间,但最近开始广泛应用于各种设备中。据最新报道,Pixel Watch 2可能会搭载UWB模块,这也许不会是谷歌唯一一款采用这项技术的新产品。 除了智

    2024年02月12日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包