【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,我们来看看执行结果:

    我们发现,最后结果并不是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;
}

我们来看看执行效果:

我们可以发现,当我们使用原子锁的时候,两个线程操作同一个数据,就不会出现结果丢失的问题了。但是我们也不难发现,执行结果慢了很多,这是因为执行过程中多了很多等待事件,这个等待我们在互斥中会讲到。

原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间,只能有一个线程访问

二.互斥体

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

参数bInitialOwner介绍:

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

互斥体特性介绍:

  1. 在任何一个时间点上,只能由一个线程拥有互斥体
  2. 当前任何一个线程不拥有互斥体是,互斥体句柄有信号
  3. 谁先等候互斥体,谁先获取
  1. 等候互斥体:
    上一篇介绍过了,使用等候句柄函数。WaitFor...
  2. 释放互斥体
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;
}

我们来看看不适用互斥体技术的时候的输出:

我们再来看看使用了互斥体之后:

#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;
}

我们可以发现使用互斥体之后,对代码段进行了枷锁。

我们来大致讲解一下互斥体的实现吧:

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


相关文章
|
2月前
|
安全 Java 开发者
在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制
【10月更文挑战第3天】在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制,如`synchronized`关键字、`Lock`接口及其实现类(如`ReentrantLock`),还有原子变量(如`AtomicInteger`)。这些工具可以帮助开发者避免数据不一致、死锁和活锁等问题。通过合理选择和使用这些机制,可以有效管理并发,确保程序稳定运行。例如,`synchronized`可确保同一时间只有一个线程访问共享资源;`Lock`提供更灵活的锁定方式;原子变量则利用硬件指令实现无锁操作。
25 2
|
2月前
|
监控 关系型数据库 MySQL
PowerShell 脚本编写 :自动化Windows 开发工作流程
PowerShell 脚本编写 :自动化Windows 开发工作流程
53 0
|
2月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
69 0
|
2月前
|
Ubuntu Linux Python
如何利用wsl-Ubuntu里conda用来给Windows的PyCharm开发
如何在WSL(Windows Subsystem for Linux)的Ubuntu环境中使用conda虚拟环境来为Windows上的PyCharm开发设置Python解释器。
118 0
|
3月前
|
监控 Java
线程池中线程异常后:销毁还是复用?技术深度剖析
在并发编程中,线程池作为一种高效利用系统资源的工具,被广泛用于处理大量并发任务。然而,当线程池中的线程在执行任务时遇到异常,如何妥善处理这些异常线程成为了一个值得深入探讨的话题。本文将围绕“线程池中线程异常后:销毁还是复用?”这一主题,分享一些实践经验和理论思考。
146 3
|
3月前
|
存储 安全 程序员
Windows任务管理器开发原理与实现
Windows任务管理器开发原理与实现
|
4月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
75 0
|
4月前
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
200 0
|
4月前
|
C# Windows 开发者
当WPF遇见OpenGL:一场关于如何在Windows Presentation Foundation中融入高性能跨平台图形处理技术的精彩碰撞——详解集成步骤与实战代码示例
【8月更文挑战第31天】本文详细介绍了如何在Windows Presentation Foundation (WPF) 中集成OpenGL,以实现高性能的跨平台图形处理。通过具体示例代码,展示了使用SharpGL库在WPF应用中创建并渲染OpenGL图形的过程,包括开发环境搭建、OpenGL渲染窗口创建及控件集成等关键步骤,帮助开发者更好地理解和应用OpenGL技术。
289 0
|
4月前
|
开发者 iOS开发 C#
Uno Platform 入门超详细指南:从零开始教你打造兼容 Web、Windows、iOS 和 Android 的跨平台应用,轻松掌握 XAML 与 C# 开发技巧,快速上手示例代码助你迈出第一步
【8月更文挑战第31天】Uno Platform 是一个基于 Microsoft .NET 的开源框架,支持使用 C# 和 XAML 构建跨平台应用,适用于 Web(WebAssembly)、Windows、Linux、macOS、iOS 和 Android。它允许开发者共享几乎全部的业务逻辑和 UI 代码,同时保持原生性能。选择 Uno Platform 可以统一开发体验,减少代码重复,降低开发成本。安装时需先配置好 Visual Studio 或 Visual Studio for Mac,并通过 NuGet 或官网下载工具包。
365 0