【线程安全问题】线程互斥与线程同步技术(下)

简介: 【线程安全问题】线程互斥与线程同步技术

语法:

BOOL CloseHandle(
  [in] HANDLE hObject
);

函数说明:

hOnject:对象的有效句柄。

返回值:如果函数成功,则返回非零值。

我们来看一个新的线程安全问题:

#include <iostream>
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID);
DWORD WINAPI ThreadProc2(LPVOID);
DWORD g_value = 0;
HANDLE hMutex;
int main()
{
  hMutex = CreateMutex(NULL, FALSE, NULL);
  HANDLE Thread[2] = { 0 };
  Thread[0]=CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL);
  Thread[1]=CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL);
  WaitForMultipleObjects(2, Thread, TRUE, INFINITE);
  printf("%d", g_value);
  return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
  while (1) {
    for (int i = 0; i < 10; i++) {
      std::cout << "++ ";
      Sleep(100);
    }
    std::cout << std::endl;
  }
  return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
  while (1) {
    for (int i = 0; i < 10; i++) {
      std::cout << "-- ";
      Sleep(100);
    }
    std::cout << std::endl;
  }
  return 0;
}

我们的本意是输出十个“++”后,换行输出十个“-- ”,但是由于CPU时间片的问题,我们实际输出是这样的:

我们使用互斥体来解决:

#include <iostream>
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID);
DWORD WINAPI ThreadProc2(LPVOID);
DWORD g_value = 0;
HANDLE hMutex= CreateMutex(NULL, FALSE, NULL);
int main()
{
  HANDLE Thread[2] = { 0 };
  Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL);
  Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL);
  WaitForMultipleObjects(2, Thread, TRUE, INFINITE);
  printf("%d", g_value);
  return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
  while (1) {
    WaitForSingleObject(hMutex,INFINITE);
    for (int i = 0; i < 10; i++) {
      std::cout << "++ ";
      Sleep(100);
    }
    std::cout << std::endl;
    ReleaseMutex(hMutex);
  }
  return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
  while (1) {
    WaitForSingleObject(hMutex,INFINITE);
    for (int i = 0; i < 10; i++) {
      std::cout << "-- ";
      Sleep(100);
    }
    std::cout << std::endl;
    ReleaseMutex(hMutex);
  }
  return 0;
}

我们来看看运行效果:

可以发现我们使用互斥体解决了该线程安全问题。

四. 事件

前面介绍的都是线程互斥技术,在我们实际开发的过程中,有很多地方是有好几个线程相互依赖,这时候就需要线程同步技术了,这里首先我们来介绍事件:

这里给出官方文档地址:事件对象 (同步)

事件我个人理解为就是一个通知,当两个线程相互依赖的时候,其中一个线程完成了工作,就将事件设置为有信号(可以理解为发出了通知)另一个线程等待到事件消息后,开始工作。

事件的使用也较为简单,主要包括以下操作:

1. 创建事件

使用CreatEvent()函数,这里给出官方文档地址:createEventA 函数 (synchapi.h)

函数功能:创建或打开命名或未命名的事件对象

语法:

HANDLE CreateEventA(
  [in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes,
  [in]           BOOL                  bManualReset,
  [in]           BOOL                  bInitialState,
  [in, optional] LPCSTR                lpName
);
6

参数说明:

lpEventAttributes:安全属性

bManualReset:如果设置为TRUE,则操作系统会自动重置事件,如果设置为FALSE,则需要程序员手动设置事件。

bInitialState:如果此参数为 TRUE,则会向事件对象发出初始状态信号;否则,它将不进行签名。

lpName:为事件命名

返回值:如果函数成功,则将返回事件对象句柄,如果函数失败,则返回NULL

2.多线程中使用事件

  • 当在多线程中使用事件时,我们需要设置事件信号:
    使用SetEvent()函数,这里给出官方文档地址:SetEvent 函数 (synchapi.h)

函数功能:将指定的事件对象设置为有信号状态。

语法:

BOOL SetEvent(
  [in] HANDLE hEvent
);

参数说明:

hEnent:指定要设置的事件对象。

返回值:如果函数成功,则返回非零值,如果函数失败,则返回NULL。

当事件对象有信号后,在其他线程中可以打开事件对象:

使用OpenEvent()函数,这里给出官方文档地址:OpenEventA 函数 (synchapi.h)

函数功能:打开现有的命名事件对象

语法:

HANDLE OpenEventA(
  [in] DWORD  dwDesiredAccess,
  [in] BOOL   bInheritHandle,
  [in] LPCSTR lpName
);

参数说明:

dwDesireAccess:访问事件对象。

bInheritHandle:如果此值为 TRUE,则此过程创建的进程将继承句柄。 否则,进程不会继承此句柄。

lpName:要打开的事件的名称。 名称比较区分大小写。

返回值:如果函数成功,则将返回事件对象的句柄,如果函数失败,则将返回NULL。

  • 当一个线程使用事件对象结束后,可以将事件复位(无消息状态)
    使用ResetEnent函数,这里给出官方文档地址:ResetEvent 函数 (synchapi.h)

函数功能:将事件对象设置为非对齐状态(无消息状态)

语法:

BOOL ResetEvent(
  [in] HANDLE hEvent
);

参数说明:

hEnent:要设置的事件句柄。

返回值:如果函数成功,则返回非零值,如果函数失败,则返回NULL。

  • 当事件使用结束后,可以使用CloseHandel函数来关闭事件。

我们来看看事件的使用:

这里创建了两个线程,当一个线程将全局变量g_value增加到100后,通知另一个线程工作。

#include <iostream>
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID);
DWORD WINAPI ThreadProc2(LPVOID);
HANDLE hEvent = 0;
DWORD g_value = 0;
int main()
{
  hEvent = CreateEvent(NULL, FALSE, 0, NULL);
  HANDLE Thread[2] = { 0 };
  Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL);
  Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL);
  WaitForMultipleObjects(2, Thread, TRUE, INFINITE);
  printf("%d", g_value);
  CloseHandle(hEvent);
  return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
  for (int i = 0; i < 100; i++) {
    g_value++;
  }
  SetEvent(hEvent);
  printf("g_value已达到100,将进行输出\n");
  return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
  WaitForSingleObject(hEvent, INFINITE);
  while (g_value<120) {
    for (int i = 0; i < 10; i++) {
      std::cout << "-- ";
      Sleep(1);
    }
    g_value++;
    std::cout << std::endl;
  }
  ResetEvent(hEvent);
  return 0;
}

我们来看看这两个线程之间的协调工作:

五. 信号量

我们之前的线程同步和线程互斥技术,都是创建了对应的对象后,被相关线程获取后即可使用,那么有没有一种技术,能够指定被获取多少次呢?这样我们就可以控制相关的线程执行多少次了。

答案是有的,我们称之为信号量,这里给出官方文档地址:信号灯对象

信号灯对象是一个同步对象,用于维护零和指定最大值之间的计数。 每次线程完成信号灯对象的等待时,计数都会递减,每次线程释放信号灯时递增。 当计数达到零时,不会再成功等待信号灯对象状态发出信号。 当信号量计数大于零时,会将信号量的状态设置为已发出信号;当信号量计数为零时,会将信号量的状态设置为未发出信号。

那么当我们使用信号量对象时,操作主要包括为:创建信号量,增加信号量的计数,等待信号量,离开信号量,关闭信号量。

1. 创建信号量

使用CreateSemaphore函数,这里给出官方文档地址:createSemaphoreA 函数 (winbase.h)

语法:

HANDLE CreateSemaphoreA(
  [in, optional] LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  [in]           LONG                  lInitialCount,
  [in]           LONG                  lMaximumCount,
  [in, optional] LPCSTR                lpName
);

参数说明:

lpSemaphoreAttributes:安全属性

lInitialCount:信号量的初始计数,此值必须小于lMaximumCount。

lMaximumCOunt:信号量的最大数量

lpName:为信号量命名

返回值:若函数成功,则返回信号量句柄,若函数失败,则返回NULL。

2. 等待信号量对象

使用WaitForSingluObject()函数,前文已经介绍过,这里不再赘述。

3.增加信号量数量计数

使用ReleaseSemaphore函数,这里给出官方文档地址:ReleaseSemaphore 函数 (synchapi.h)

函数功能:按指定量增加指定信号量的计数

语法:

BOOL ReleaseSemaphore(
  [in]            HANDLE hSemaphore,
  [in]            LONG   lReleaseCount,
  [out, optional] LPLONG lpPreviousCount
);

参数说明:

hSemaphore:指定信号量的句柄

lReleaseCount:指定要增加的数量

lpPreviousCount:这是一个OUT类型的参数,用于接收上一个计数,也就是增加之前的信号量计数

返回值:若函数成功,则返回非零值,若函数失败,则返回NULL。

4.关闭信号量

使用CloseHandle()函数,前文已经介绍过,这里不再做赘述。

我们来看看信号量的使用:

#include <iostream>
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID);
DWORD WINAPI ThreadProc2(LPVOID);
HANDLE hSemaphore = 0;
DWORD g_value = 0;
int main()
{
  hSemaphore = CreateSemaphore(NULL, 0, 3, NULL);
  HANDLE Thread[2] = { 0 };
  Thread[0] = CreateThread(NULL, 0, ThreadProc1, 0, NULL, NULL);
  Thread[1] = CreateThread(NULL, 0, ThreadProc2, 0, NULL, NULL);
  WaitForMultipleObjects(2, Thread, TRUE, INFINITE);
  printf("%d", g_value);
  CloseHandle(hSemaphore);
  return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
  for (int i = 0; i < 100; i++) {
    g_value++;
  }
  printf("g_value已达到100,将进行输出\n");
  ReleaseSemaphore(hSemaphore, 2, NULL);
  return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
  for (int k = 0; k < 3; k++) {
    WaitForSingleObject(hSemaphore, INFINITE);
    for (int i = 0; i < 10; i++) {
      std::cout << "-- ";
      Sleep(1);
    }
    std::cout << std::endl;
  }
  return 0;
}

我们来看看这段代码:我们本意是,当线程2执行的时候,输出三行,但是我们设置了信号量为2,所以只能输出两行。

我们看看执行效果:

本篇文章就分享到这里,如果大家发现其中有错误或者是个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望我们共同进步!!!

相关文章
|
1天前
|
安全 Java
java线程之线程安全
java线程之线程安全
14 1
|
7天前
|
Java
【技术瑜伽师】Java 线程:修炼生命周期的平衡之道,达到多线程编程的最高境界!
【6月更文挑战第19天】Java多线程编程犹如瑜伽修行,从创建线程开始,如`new Thread(Runnable)`,到启动线程的活跃,用`start()`赋予生命。面对竞争与冲突,借助同步机制保证资源访问的有序,如`synchronized`关键字。线程可能阻塞等待,如同瑜伽的静止与耐心。完成任务后线程终止,整个过程需密切关注状态变换,以求多线程间的和谐与平衡。持续修炼,如同瑜伽般持之以恒,实现高效稳定的多线程程序。
|
9天前
|
API
linux---线程互斥锁总结及代码实现
linux---线程互斥锁总结及代码实现
|
7天前
|
Java 开发者
【技术成长日记】Java 线程的自我修养:从新手到大师的生命周期修炼手册!
【6月更文挑战第19天】Java线程之旅,从新手到大师的进阶之路:始于创建线程的懵懂,理解就绪与运行状态的成长,克服同步难题的进阶,至洞悉生命周期的精通。通过实例,展示线程的创建、运行与同步,展现技能的不断提升与升华。
|
7天前
|
Java
【技术解码】Java线程的五味人生:新建、就绪、运行、阻塞与死亡的哲学解读!
【6月更文挑战第19天】Java线程生命周期如同人生旅程,经历新建、就绪、运行、阻塞至死亡五阶段。从`new Thread()`的诞生到`start()`的蓄势待发,再到`run()`的全力以赴,线程在代码中奔跑。阻塞时面临挑战,等待资源释放,最终通过`join()`或中断结束生命。线程的每个状态转变,都是编程世界与哲思的交汇点。
|
13天前
|
存储 安全 Java
Java多线程中线程安全问题
Java多线程中的线程安全问题主要涉及多线程环境下对共享资源的访问可能导致的数据损坏或不一致。线程安全的核心在于确保在多线程调度顺序不确定的情况下,代码的执行结果始终正确。常见原因包括线程调度随机性、共享数据修改以及原子性问题。解决线程安全问题通常需要采用同步机制,如使用synchronized关键字或Lock接口,以确保同一时间只有一个线程能够访问特定资源,从而保持数据的一致性和正确性。
|
1天前
|
存储 安全 Java
Java中的线程安全与同步技术
Java中的线程安全与同步技术
|
22天前
|
安全 Java 容器
多线程(进阶四:线程安全的集合类)
多线程(进阶四:线程安全的集合类)
17 0
|
26天前
|
安全 算法 Java
Java中的并发编程技术:解锁高效多线程应用的秘密
Java作为一种广泛应用的编程语言,其并发编程技术一直备受关注。本文将深入探讨Java中的并发编程,从基本概念到高级技巧,帮助读者更好地理解并发编程的本质,并学会如何在多线程环境中构建高效可靠的应用程序。
|
5天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。