Win32和c++11多线程
- 一、概念
- 1.线程的特点
- 线程内核对象
- 线程控制块
- 线程是独立调度和分派的基本单位
- 共享进程的资源
- 2.线程的上下文切换
- 引起上下文切换的原因
- 3.线程的状态
- 二、Windows多线程API
- 1.CreateThread创建线程
- 2.获取线程ID
- 3.关闭线程句柄
- 4.挂起线程
- 5.恢复线程
- 6.休眠线程的执行
- 7.WaitForSingleObject
- 8.终止线程
- 9.获取线程结束码
- 10.WaitForMultipleObjects
- 11.\_beginthread和\_endthread
- CreateThread不安全
- _beginthread
- _endthread
- 三、多线程模拟火车站售票
- 1.介绍
- 2.实现
- 3.为什么会出现卖出了第0张票?
- 四、多线程之间的同步和互斥
- 1.临界区
- 临界区结构对象
- 初始化临界区
- 进入和离开临界区
- 尝试进入临界区
- 区别
- 删除临界区
- 2.线程死锁
- 死锁产生的必要条件
- 3.信号量
- 临界区与信号量对比
- 相关API
- (1)创建信号量
- (2)P操作
- (3)V操作
- 实现进程或线程只有一个实例
- 4.互斥量mutex
- 相关API
- (1)创建互斥量
- (2)获得互斥量
- (3)释放互斥量
- 示例
- 利用互斥量实现进程只有一个实例
- 5.事件Event
- 有信号状态和无信号状态
- 相关API
- (1)创建事件
- (2)把指定的事件对象设置为有信号状态
- (3)把指定的事件对象设置为无信号状态
- (4)等待事件对象的句柄
- 自动重置事件
- 手动重置事件
- 实现进程只有一个实例
- 6.PV操作
- 生产者消费者问题
- 7.总结
- 五、线程本地存储
- 1.静态TLS
- 2.动态TLS
- 六、多线程间的消息通讯
- 示例:一个线程向另一个线程发送消息
- 七、C++11多线程
一、概念
进程要想执行任务,必须得有线程,线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
1.线程的特点
线程内核对象
线程控制块
线程是独立调度和分派的基本单位
共享进程的资源
2.线程的上下文切换
引起上下文切换的原因
3.线程的状态
二、Windows多线程API
头文件#include<Windows.h>
1.CreateThread创建线程
参数说明:
线程的句柄是一块地址,线程ID可以用GetCurrentThreadId()函数获得。
2.获取线程ID
3.关闭线程句柄
关闭句柄后线程还会继续执行。
4.挂起线程
5.恢复线程
6.休眠线程的执行
7.WaitForSingleObject
等待一个内核对象变为已通知状态。这个函数常用于线程同步,确保一个线程在继续执行之前等待某个事件(如线程结束、互斥体释放、信号量达到等)。
未通知状态:该句柄关联的线程未结束,仍在执行。
已通知状态:该句柄关联的线程执行结束。
参数:
hHandle
:等待的对象的句柄。这个句柄可以是各种同步对象,如事件、互斥体、信号量、进程或线程。dwMilliseconds
:超时时间,以毫秒为单位。如果设置为INFINITE
,表示无限等待,直到对象进入信号状态。
返回值:
WaitForSingleObject
返回一个 DWORD
值,表示函数的结果。常见的返回值包括:
WAIT_OBJECT_0
:指定的对象已进入信号状态。WAIT_TIMEOUT
:等待超时,指定的对象在超时时间内未进入信号状态。WAIT_FAILED
:函数调用失败。可以通过调用GetLastError
函数获取扩展错误信息。
8.终止线程
9.获取线程结束码
10.WaitForMultipleObjects
参数说明:
11._beginthread和_endthread
CreateThread不安全
_beginthread
参数说明:
返回值:
_endthread
三、多线程模拟火车站售票
1.介绍
2.实现
3.为什么会出现卖出了第0张票?
四、多线程之间的同步和互斥
1.临界区
临界区结构对象
初始化临界区
进入和离开临界区
如果这样加锁,那么只要有一个线程进入临界区,除非所有票卖完,否则不会释放临界区。
如果这样加锁,又会出现卖出第0张票的情况。
最后这种情况,修改代码,在进入临界区后再次判断,可以避免上述情况。
尝试进入临界区
区别
删除临界区
2.线程死锁
死锁产生的必要条件
3.信号量
临界区与信号量对比
-
临界区
-
用于保护共享资源的代码块,确保在同一时间只有一个线程能够执行该代码块。
-
通常用于同一进程内的线程同步。
-
-
信号量
-
是一种更通用的同步机制,允许多个线程同时访问一定数量的共享资源。
-
可以用于进程间同步(IPC)。
-
相关API
(1)创建信号量
(2)P操作
(3)V操作
实现进程或线程只有一个实例
虽然每个进程有自己的地址空间,但命名对象(如命名信号量、命名互斥体等)是在系统范围内共享的。这意味着即使进程有各自的地址空间,命名对象在创建时会注册在操作系统的命名空间中,其他进程可以通过同样的名字访问这些对象。
在 Windows 操作系统中,命名对象(包括信号量、互斥体、事件等)在系统命名空间中共享。也就是说,当一个进程创建一个命名信号量时,操作系统会将该信号量注册在全局命名空间中。其他进程如果尝试创建或打开同名的信号量,就可以访问到这个信号量。
4.互斥量mutex
相关API
(1)创建互斥量
bInitialOwner:指定调用线程是否在互斥对象的初始状态下获得所有权。如果这个值为 TRUE
,调用线程在互斥对象创建成功后立即获得所有权;否则,互斥对象的初始状态为非信号状态。
(2)获得互斥量
(3)释放互斥量
示例
利用互斥量实现进程只有一个实例
5.事件Event
有信号状态和无信号状态
在 Windows 操作系统中,事件对象用于线程同步,其状态可以是“有信号”(signaled)或“无信号”(nonsignaled)。这两种状态用于控制线程的执行,具体如下:
- 当事件对象处于有信号状态时,所有等待该事件的线程都将被解除阻塞,并继续执行。这意味着事件发生了,等待的线程可以继续进行它们的工作。
- 当事件对象处于无信号状态时,所有等待该事件的线程都将被阻塞,直到事件对象的状态变为有信号。这意味着事件尚未发生,等待的线程需要等待,直到事件发生。
事件对象可以分为两种类型:自动重置事件(auto-reset event)和手动重置事件(manual-reset event)。这两种类型的事件对象在状态变更和重置机制上有所不同。
- 当事件对象处于有信号状态时,等待的线程将被解除阻塞,然后事件对象自动重置为无信号状态。如果有多个线程在等待事件,只有一个线程会被解除阻塞。
- 当事件对象处于有信号状态时,所有等待的线程将被解除阻塞,并且事件对象保持有信号状态,直到显式调用
ResetEvent
函数将其状态重置为无信号状态。
相关API
(1)创建事件
(2)把指定的事件对象设置为有信号状态
(3)把指定的事件对象设置为无信号状态
(4)等待事件对象的句柄
自动重置事件
手动重置事件
实现进程只有一个实例
6.PV操作
生产者消费者问题
7.总结
五、线程本地存储
1.静态TLS
2.动态TLS
六、多线程间的消息通讯
示例:一个线程向另一个线程发送消息
使用PeekMessage写法如下: