第四章:使用Windows Threads编程

Download Report

Transcript 第四章:使用Windows Threads编程

多核结构与程序设计
多核结构与程序设计
杨全胜
http://www.njyangqs.com/
东南大学成贤学院计算机系
Southeast University
东 南 大 学
1
http://www.njyangqs.com/
多核结构与程序设计
使用Windows Threads编程

内容
 Windows
threads库
 使用Windows thread API
 同步
Southeast University
东 南 大 学
2
http://www.njyangqs.com/
多核结构与程序设计
Windows threads库



Win32 API是内核与应用程序之间的接口。
它是一个能够被应用程序调用的系统函数包。
MFC是微软基本函数类库,它把Win32 API
封装成类。
.NET框架包含公共语言运行库(CLR)和框架类
库(FCL)。.NET基本类中的
System.Threading命名空间提供支持线程的
大量类和接口。
Southeast University
东 南 大 学
3
http://www.njyangqs.com/
多核结构与程序设计
使用Windows Threads编程

内容
 Windows
threads库
 使用Windows thread API
 同步
Southeast University
东 南 大 学
4
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

Win32* HANDLE类型
Object对象都被以HANDLE(句柄)
类型的变量引用。
 每个Windows
 指向内核的Object(对象)
 Thread,
process, file, event, mutex,
semaphore等.
 Object创建函数返回HANDLE
 Object通过它的handle(句柄)来控制
 不要直接操作object,应利用句柄使用各类函数
Southeast University
东 南 大 学
5
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

创建Win32* 线程
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId ) ;
lpThreadAttributes: 新线程的安全属性
dwStackSize: 新线程的栈大小
lpStartAddress : 一个指向线程函数以及启动线程后在代码段的起始地址的指针
pvParam : 传递到线程函数的参数
dwCreationFlags : 创建标志(0或CREATE_SUSPENDED).
lpThreadId :[输出] 指向创建函数返回的线程标识的变量的指针
Southeast University
东 南 大 学
6
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

LPTHREAD_START_ROUTINE参数
 CreateThread() 希望该参数指向一个全局函数
(线程函数),该函数:
 返回DWORD
WINAPI
 只有一个参数:LPVOID (void *)
 调用的类型是
线程函数:
DWORD WINAPI ThreadFunc (LPVOID lpvThreadParm);
Southeast University
东 南 大 学
7
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

使用明确的线程
 明确标识出可以并行执行的代码部分
 将这些代码封装到函数中
 如果代码已经是一个函数,需要写一个驱动函数来
协调多个线程的工作。
 增加CreateThread(
) 调用来指派线程到执行函
数
Southeast University
东 南 大 学
8
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

管理线程
 设置优先权
 线程优先权
= 进程优先权 + 线程相对优先权
Bool SetThreadPriority (HANDLE hThread , int nPriority)
为指定的线程设置优先权值
hThread: 要修改优选权值的线程的句柄
nPriority: 为线程设置的优先权值
Southeast University
东 南 大 学
9
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

管理线程
 挂起和恢复线程
(suspend count)=0, 线
程被执行. 如果它>0, 调度器将不会调度这个线程
 如果线程中的挂起计数器
DWORD SuspendThread(HANDLE hThread);
挂起指定的线程
hThread: 要挂起的线程的句柄.
DWORD ResumeThread(HANDLE hThread);
递减一个线程的suspend count. 当suspend count减到0,恢复线程
的执行
hThread: 要被恢复的线程的句柄
Southeast University
东 南 大 学
10
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

管理线程
 等待线程
 等某一个object
(thread)
DWORD WaitForSingleObject( HANDLE hHandle,
DWORD dwMilliseconds );
当指定的object 是可用状态(比如线程退出)或者时间间隔已经过
去,该函数返回,否则等待
hHandle: object的句柄.
dwMilliseconds: 毫秒级的时间间隔
Southeast University
东 南 大 学
11
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

管理线程
 等待线程
 等多个objects
(线程)
DWORD WaitForMultipleObjects( DWORD nCount,
const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds );
当指定的多个object中任一个进入可用状态(线程退出)或者时间间隔已过,
函数返回,否者等待
nCount: 数组指针lpHandles中的object句柄个数.
lpHandles: 指向一个object句柄的数组的指针.
bWaitAll: 如果该参数是TRUE, 则只有lpHandles数组中所有object都可用了
函数才返回,如果是FALSE,则只要任意一个object状态是可用,就返回。
dwMilliseconds: 毫秒级的时间间隔.
Southeast University
http://www.njyangqs.com/
12
东 南 大 学
多核结构与程序设计
使用Windows thread API

管理线程
 退出一个线程
ExitThread(DWORD dwExitCode)
结束一个线程.
dwExitCode: 给调用线程的退出码
TerminateThread(HANDLE hThread, DWORD dwExitCode)
终止一个线程.
hThread: 要终止的线程的句柄.
dwExitCode:给调用线程的退出码
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode );
获取指定线程的终止状态.
hThread: 线程的句柄
lpExitCode: 接收线程终止状态的变量
Southeast University
东 南 大 学
13
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

管理线程
 杀死线程
 释放操作系统资源
• 在程序完成之前清理线程
• 进程退出的时候做这件事
CloseHandle(HANDLE hObject);
关闭一个打开的object句柄
hObject: Handle to an open object.
Southeast University
东 南 大 学
14
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

下面这个程序首先创建两个线程,当输入为1时,执行线程,
否则挂起线程 #include <windows.h>
#include <iostream>
using namespace std;
Southeast University
东 南 大 学
DWORD WINAPI FunOne(LPVOID param){
while(true)
{
Sleep(1000);
cout<<"hello! ";
}
return 0;
}
DWORD WINAPI FunTwo(LPVOID param){
while(true)
{
Sleep(1000);
cout<<"world! ";
}
return 0;
}
15
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API
int main(int argc, char* argv[])
{
int input=0;
HANDLE hand1=CreateThread (NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);
HANDLE hand2=CreateThread (NULL, 0, FunTwo, (void*)&input, CREATE_SUSPENDED, NULL);
while(true){
cin>>input;
if(input==1)
{
ResumeThread(hand1);
ResumeThread(hand2);
}
else
{
SuspendThread(hand1);
SuspendThread(hand2);
}
};
TerminateThread(hand1,1);
TerminateThread(hand2,1);
return 0;
}
Southeast University
东 南 大 学
16
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

案例: 创建线程
#include <stdio.h>
#include <windows.h>
DWORD WINAPI helloFunc(LPVOID arg) {
printf(“Hello Thread”);
return 0;
}
Main() {
HANDLE hThread =
CreateThread(NULL, 0, helloFunc, NULL, 0, NULL);
}
会发生什么情况?
Southeast University
东 南 大 学
17
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

案例解释
 主线程是进程
 当进程结束,所有线程结束
 需要有办法能等待线程结束
Southeast University
东 南 大 学
18
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

等待Windows*线程
#include <stdio.h>
#include <windows.h>
BOOL threadDone = FALSE;
DWORD WINAPI helloFunc(LPVOID arg) {
printf(“Hello Thread”);
threadDone = TRUE;
return 0;
}
不是个好方法!
Main() {
HANDLE hThread =
CreateThread(NULL, 0, helloFunc, NULL, 0, NULL);
While (!threadDone); // wasted cycles
}
Southeast University
东 南 大 学
19
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

等待线程
 等待单个object
(线程)
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds );
 调用线程等待(阻塞)直到:
 时间到
• 返回码通常会指出这一点
 线程退出(句柄被释放)
• 用INFINITE来等待直到线程结束
 不用CPU时钟
Southeast University
东 南 大 学
20
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

等待多个线程
 等待多达64个objects
(线程)
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE* lpHandles, // array
BOOL bWaitAll, // wait for one or all
DWORD dwMilliseconds );
bWaitAll =TRUE
 等待部分: bWaitAll = FALSE
 返回的值是找到的第一个数组索引
 等待所有:
Southeast University
东 南 大 学
21
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

WaitFor*函数注意事项
 句柄作为参数
 适用于不同类型的对象(objects)
 核心objects
有两种状态
 Signaled
 Non-signaled
 行为被句柄指明的object所定义
 线程:
signaled意味着终止
Southeast University
东 南 大 学
22
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

案例: 多线程
#include <stdio.h>
#include <windows.h>
const int numThread = 4;
DWORD WINAPI helloFunc(LPVOID arg) {
printf(“Hello Thread”);
return 0;
}
Main() {
HANDLE hThread[numThread];
for (int i = 0; i < numThread; i++)
hThread[i] =
CreateThread(NULL, 0, helloFunc, NULL, 0, NULL);
WaitForMultipleObjects(numThreads, hThread, TRUE, INFINITE);
}
Southeast University
东 南 大 学
23
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

怎么打印线程号?
?
=>
Southeast University
东 南 大 学
24
http://www.njyangqs.com/
多核结构与程序设计
使用Windows thread API

这样对吗?为什么?
#include <stdio.h>
#include <windows.h>
const int numThread = 4;
DWORD WINAPI helloFunc(LPVOID pArg) {
int* p = (int *)pArg;
int mynum = *p;
printf("Hello Thread %d\n",mynum);
return 0;
}
main() {
HANDLE hThread[numThreads];
for (int i = 0; i < numThreads; i++)
hThread[i] =
CreateThread(NULL, 0, helloFunc, &i, 0, NULL );
}
Southeast University
http://www.njyangqs.com/
25
东 南 大 学
多核结构与程序设计
使用Windows thread API

Hello线程的时间轴
Time
main
Thread 0
Thread 1
T0
T1
T2
T3
T4
i = 0
create(&i)
i++ (i == 1)
create(&i)
i++ (i == 2)
---------launch
T5
create(&i)
----launch
p = pArg
myNum = *p
myNum = 2
print(2)
exit
T6
i++ (i == 3)
Southeast University
东 南 大 学
26
p = pArg
myNum = *p
myNum = 3
http://www.njyangqs.com/
多核结构与程序设计
使用Windows Threads编程

内容
 Windows
threads库
 使用Windows thread API
 同步
Southeast University
东 南 大 学
27
http://www.njyangqs.com/
多核结构与程序设计
同步

线程化的问题
线程化问题
数据竞争
死锁
粒度
负载均衡
活锁
多线程应用的线程化问题
Southeast University
东 南 大 学
28
http://www.njyangqs.com/
多核结构与程序设计
同步

竞争条件
 多个线程对相同变量的并发访问
 读/写冲突
 写/写冲突
 并发程序中最常见的错误
 并不是所有时候都表现出来
 可以用Intel®
Thread Checker来检测执行时候
的数据竞争
Southeast University
东 南 大 学
29
http://www.njyangqs.com/
多核结构与程序设计
同步

如何避免数据竞争
 把变量的作用域限制在线程区域内
 变量在线程函数内声明
 在线程栈中分配
 TLS
(Thread Local Storage,线程局部存储)
 用临界区控制共享访问
 互斥和同步
 锁、信号量、事件、临界段、互斥量…
Southeast University
东 南 大 学
30
http://www.njyangqs.com/
多核结构与程序设计
同步

解决办法 – “局部” 存储
DWORD WINAPI helloFunc(LPVOID pArg)
{
int mynum = *((int *)pArg);
printf("Hello Thread %d\n",mynum);
return 0;
}
// from main
int tNum[numThreads];
for (int i = 0; i < numThreads; i++){
tNum[i] = i;
hThread[i] =
CreateThread(NULL, 0, helloFunc, &tNum[i], 0, NULL );
}
WaitForMultipleObjects(numThreads, hThread, TRUE, INFINITE);
Southeast University
东 南 大 学
31
http://www.njyangqs.com/
多核结构与程序设计
同步

Win32* 的互斥量(Mutexes)
 Win32互斥量是由句柄指向的核心对象(Kernel
object)
 当其可用的时候标记为“Signaled”
 除线程外,还在进程之间共享
 操作:
CreateMutex(…) // 产生一个新的
 WaitForSingleObject(…) // 等待并上锁
 WaitForMultipleObjects(…) //等待多个
 ReleaseMutex(…) // 开锁

Southeast University
东 南 大 学
32
http://www.njyangqs.com/
多核结构与程序设计
同步

Win32* 的互斥量(Mutexes)
HANDLE WINAPI CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCSTR lpName);
// text name for object
创建或打开一个命名或无名的互斥量mutex
lpMutexAttributes: 指向决定返回的句柄是否能够被子女继承的
SECURITY_ATTRIBUTES 结构的指针.
bInitialOwner: 如果该值为TRUE 并且调用者创建了互斥量mutex, 调
用的线程将获得互斥量mutex对象的初始所有权。
lpName: 到NULL结束字符串的指针,该字符串为mutex对象命名。
BOOL ReleaseMutex( HANDLE hMutex );
释放指定互斥量mutex对象的所有权。
hMutex: mutex对象的句柄.
Southeast University
东 南 大 学
33
http://www.njyangqs.com/
多核结构与程序设计

案例:mutex
DWORD WINAPI ThreadFunc1(PVOID pvParam ) {
HANDLE *phMutex = (HANDLE *) pvParam;
for(int i=1;i<=100;i++) {
WaitForSingleObject( *phMutex, INFINITE);
cout<<“Thread1 output;”<<i<<endl;
ReleaseMutex(*phMutex);
DWORD WINAPI ThreadFunc2(…) {
}
HANDLE *phMutex = (HANDLE *) pvParam;
return 0;
…
}
}
main() {
HANDLE hMutex=CreateMutex(NULL,FALSE,”DisplayMutex”);
HANDLE ThreadHandl1=CraeteThread(NULL,0,ThreadFun1,&hMutex,0,NULL);
HANDLE ThreadHandl2=CraeteThread(NULL,0,ThreadFun2,&hMutex,0,NULL);
HANDLE ThreadHandle[2] ={ThreadHandl1,ThreadHandl2}
WaitForMultipleObjects(NUMTHREADS, hThread, TRUE, INFINITE);
CloseHandle(hMutex); return 0;
}
Southeast University
东 南 大 学
34
http://www.njyangqs.com/
多核结构与程序设计
同步

Win32* 临界区(Critical Section)
 Win32临界区是轻量级的,进程内只互斥
 Win32临界区是最有用和最常用的
 新类型
CRITICAL_SECTION cs ;
 创建和撤销操作
void InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
初始化一个critical section对象.
lpCriticalSection: 指向critical section对象的指针.
void DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
释放所有的被无主critical section对象使用的资源.
lpCriticalSection:指向critical section对象的指针.
Southeast University
东 南 大 学
35
http://www.njyangqs.com/
进入临界区
同步

多核结构与程序设计
临界区
Win32* 临界区
 进入临界区
退出临界区
void EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection );
 当有另一个线程在临界区中时阻塞
 当没有线程在临界区的时候返回
 退出临界区
void LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection );

必须从获得临界区使用权的线程中退出临界区
Southeast University
东 南 大 学
36
http://www.njyangqs.com/
多核结构与程序设计
同时

案例: Critical Section
#define NUMTHREADS 4
CRITICAL_SECTION g_cs;
// why does this have to be global?
int g_sum = 0;
DWORD WINAPI threadFunc(LPVOID arg ) {
int mySum = bigComputation();
EnterCriticalSection(&g_cs);
g_sum += mySum;
// threads access one at a time
LeaveCriticalSection(&g_cs);
return 0;
}
main() {
HANDLE hThread[NUMTHREADS];
InitializeCriticalSection(&g_cs);
for (int i = 0; i < NUMTHREADS; i++)
hThread[i] =
CreateThread(NULL,0,threadFunc,NULL,0,NULL);
WaitForMultipleObjects(NUMTHREADS, hThread, TRUE, INFINITE);
DeleteCriticalSection(&g_cs);
Southeast University
}
http://www.njyangqs.com/
37
东 南 大 学
多核结构与程序设计
同步
数值积分案例

4.0
1

4.04.0
f(x) = 2 dx = 
(1+x
) 2)
(1+x
static long num_steps=100000;
double step, pi;
0
void main()
{ int i;
double x, sum = 0.0;
2.0
step= 1.0/(double) num_steps; //delta x
 tan(
)  tan(
) i<1num_steps; i++){
for (i=0;
x =4(i+0.5)*step; //每个矩形中间点
sum = sum +14.0/(1.0 +x*x);
1 4.0
// 计算矩形中间点的Y值的和

 4.0  arctan(
x)  4.0   4.0  0  
2
0 1 x
}
0
4
pi = step * sum;
//所有矩形的面积,近似曲线内图形的面积
1.0
printf(“Pi = %f\n”,pi);
}
http://www.njyangqs.com/
O
45

0.0
Southeast University
东 南 大 学
X
38
多核结构与程序设计
同步

案例: 计算
static long num_steps=1000000;
double step, pi;
void main()
{ int i;
double x, sum = 0.0;
step = 1.0/(double) num_steps;
for (i=0; i< num_steps; i++){
x = (i+0.5)*step;
sum = sum + 4.0/(1.0 + x*x);
}
pi = step * sum;
printf("Pi = %12.9f\n",pi);
}
用Win32线程来并行化数
值积分代码
如何在线程间划分循环迭
代?
什么变量能够局部化?
什么变量必须对所有线程
都是可见的?
Southeast University
东 南 大 学
39
http://www.njyangqs.com/
多核结构与程序设计
同步

案例: 计算
const int gNumSteps = 100000;
const int gNumThreads = 4;
double gStep = 0.0;
double gPi = 0.0;
CRITICAL_SECTION gCS;
Southeast University
东 南 大 学
40
http://www.njyangqs.com/
多核结构与程序设计
同步
main()
{
HANDLE threadHandles[gNumThreads];
 案例: 计算
int tNum[gNumThreads];
InitializeCriticalSection(&gCS);
gStep = 1.0 / gNumSteps;
for ( int i=0; i<gNumThreads; ++i )
{
tNum[i] = i;
threadHandles[i] = CreateThread( NULL, 0,
threadFunction, (LPVOID)&tNum[i], 0, NULL); }
WaitForMultipleObjects(gNumThreads,
threadHandles, TRUE, INFINITE);
DeleteCriticalSection(&gCS);
printf("%12.9f\n", gPi );
}
Southeast University
东 南 大 学
41
http://www.njyangqs.com/
多核结构与程序设计
同步
DWORD WINAPI threadFunction(LPVOID pArg)
{
 案例:
计算 = *((int *)pArg);
int myNum
double partialSum = 0.0, x; // local to each thread
for ( int i=myNum; i<gNumSteps; i+=gNumThreads )
{
x = (i + 0.5f) / gNumSteps;
partialSum += 4.0f / (1.0f + x*x);
}
EnterCriticalSection(&gCS);
gPi += partialSum * gStep;
LeaveCriticalSection(&gCS);
return 0;
}
Southeast University
东 南 大 学
42
http://www.njyangqs.com/
多核结构与程序设计
同步

Win32* 信号量(Semaphores)
 信号量是一个同步对象:
 用来允许多个线程进入临界区.
 用来记录可用资源的计数值(count).
 在1968年被Edsger
Dijkstra形式化.
 信号量上的两个操作
 Wait
[P(s)]: 线程等待直到s > 0, 然后 s = s-1
 Post [V(s)]: s = s + 1
 当count
> 0,信号量进入signaled状态
Southeast University
东 南 大 学
43
http://www.njyangqs.com/
多核结构与程序设计
同步

创建Win32*信号量
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount, // 初始化信号量对象的计数值 count
LONG lMaximumCount, // count的最大值
LPCTSTR lpName ); // 对象的文本名称
 IMaximumCount的值必须大于0
 lInitialCount的值必须是:
 大于等于0
 小于等于IMaximumCount
 不能超出值域范围
Southeast University
东 南 大 学
44
http://www.njyangqs.com/
多核结构与程序设计
同步

Wait和Post操作
 用WaitForSingleObject
来等待信号量
count = 0, 线程等待
 如果count > 0,则将count-1并返回
 如果
 增加信号量(Post操作)
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG cReleaseCount,
LPLONG lpPreviousCount );
+ cReleaseCount
 通过lpPreviousCount返回count的上一个值
 将信号量count
Southeast University
东 南 大 学
45
http://www.njyangqs.com/
多核结构与程序设计
同步

信号量的使用
 控制规模有限的数据结构的访问
 队列、栈、双端队列
 用count来计算可用的元素
 控制有限资源的访问
 文件描述符、磁带驱动器等…
 在一定范围内限制活动线程的数目
 二值信号量[0,1]能作为互斥量mutex
Southeast University
东 南 大 学
46
http://www.njyangqs.com/
多核结构与程序设计
同步

使用信号量注意:
 信号量没有所有权
 任何线程都可以释放信号量,而不仅是等待中最
后的线程。
 用好的编程习惯来避免这种情况
 没有无约束的信号量的概念
 如果线程在post之前终止,信号量的增加运算可能
会丢失。
 死锁
Southeast University
东 南 大 学
47
http://www.njyangqs.com/
多核结构与程序设计
同步

案例1: 信号量当互斥量
 主线程打开输入文件,等线程终止。
 线程将
 从输入文件读一行
 计算在一行中所有的5个字符的单词
Southeast University
东 南 大 学
48
http://www.njyangqs.com/
多核结构与程序设计
同步

案例1: 主程序
HANDLE hSem1, hSem2;
FILE *fd;
int fiveLetterCount = 0;
main()
{ HANDLE hThread[NUMTHREAD];
hSem1 = CreateSemaphore(NULL,1,1,NULL); // Binary semphore
hSem2 = CreateSemaphore(NULL,1,1,NULL); // Binary semphore
fd = fopen(“InFile”, “r”); // open file for read
for (int i = 0; i < NUMTHREAD; i++)
hThread[i] = CreateThread(NULL, 0,CountFives,NULL,0,NULL);
WaitForMultipleObjects(NUMTHREAD, hThread, TRUE, INFINITE);
fclose(fd);
printf(“Numbers of five letter words is %d\n”, fiveLetterCount);
Southeast
} University
http://www.njyangqs.com/
东 南 大 学
49
多核结构与程序设计
同步

案例1: 信号量
DWORD WINAPI CountFives(LPVOID arg) {
BOOL bDone = FALSE;
char inLine[132]; int lCount = 0;
while (!bDone)
{
WaitForSingleObject(hSem1, INFINITE); // access to input
bDone = (GetNextLine(fd, inLine) == EOF);
ReleaseSemaphore(hSem1, 1, NULL);
if (!bDone)
if (lCount = GetFiveLetterWordCount(inLine)) {
WaitForSingleObject(hSem2, INFINITE); // update global
fiveLetterCount += lCount;
ReleaseSemaphore(hSem2, 1, NULL);
}
}
} University
Southeast
http://www.njyangqs.com/
东 南 大 学
50
多核结构与程序设计
同步

案例2: 信号量当互斥量
 主线程打开输入文件,等线程终止。
 线程将
 从输入文件读一行
 计算在一行中所有的单词个数,奇数字母单词个数,
偶数字母单词个数
Southeast University
东 南 大 学
51
http://www.njyangqs.com/
多核结构与程序设计
同步

案例2: 变量定义与读一行
FILE *fd;
int TotalEvenWords = 0;
Int TotalOddWords = 0, TotalWords = 0;
const int NUMTHREADS = 4;
HANDLE hSem1, hSem2;
int GetNextLine(FILE *f, char *Line)
{
if (fgets(Line, 132, f)==NULL) if (feof(f))return EOF; else
return 1;
} University
Southeast
东 南 大 学
52
http://www.njyangqs.com/
多核结构与程序设计
同步

案例2: 主程序
main()
{
HANDLE hThread[NUMTHREADS];
hSem1 = CreateSemaphore(NULL, 1, 1, NULL); // Binary semaphore
hSem2 = CreateSemaphore(NULL, 1, 1, NULL); // Binary semaphore
fd = fopen("InFile1.txt", "r"); // Open file for read
for (int i = 0; i < NUMTHREADS; i++)
hThread[i] = CreateThread(NULL,0,CountWords,NULL,0,NULL);
WaitForMultipleObjects(NUMTHREADS, hThread, TRUE, INFINITE);
fclose(fd);
printf("Total Words = %8d\n\n", TotalWords);
printf("Total Even Words = %7d\nTotal Odd Words = %7d\n",
TotalEvenWords, TotalOddWords);
}
Southeast University
东 南 大 学
53
http://www.njyangqs.com/
多核结构与程序设计
同步

案例2: 信号量
DWORD WINAPI CountWords(LPVOID arg)
{ BOOL bDone = FALSE ;
char inLine[132]; int lCount = 0;
while (!bDone) {
WaitForSingleObject(hSem1, INFINITE); // access to input
bDone = (GetNextLine(fd, inLine) == EOF);
ReleaseSemaphore(hSem1, 1, NULL);
if (!bDone){
lCount = GetWordAndLetterCount(inLine) ;
WaitForSingleObject(hSem2, INFINITE); // update global
TotalOddWords += (lCount % 100); lCount /= 100;
TotalEvenWords += (lCount % 100); lCount /= 100;
TotalWords += lCount;
ReleaseSemaphore(hSem2, 1, NULL);
}
}
return 0;
Southeast
} University
http://www.njyangqs.com/
东 南 大 学
54
多核结构与程序设计
同步
int GetWordAndLetterCount(char *Line)
{ int Word_Count = 0, Letter_Count = 0;
int lTotalEvenWords = 0, lTotalOddWords = 0;
for (int i=0;i<132;i++) {
if ((Line[i]!=' ')&&(Line[i]!=0)&&(Line[i]!='\n')) Letter_Count++;
else {
if (Letter_Count % 2) {
lTotalOddWords++; Word_Count++; Letter_Count = 0;
}
else {
lTotalEvenWords++; Word_Count++; Letter_Count = 0;
}
if (Line[i]==0) break;
}
}
return (Word_Count*10000 + lTotalEvenWords*100 +
lTotalOddWords);
// encode three return values
Southeast
} University
http://www.njyangqs.com/
55
东 南 大 学
多核结构与程序设计
同步

Win32*事件(Events)
 用来发信号给其他线程说明有些事件发生:
 数据可用,消息准备好,计算结束等
 线程用WaitFor*函数等待信号
 两类事件
 自动重置
• 这类事件得到通知时,等待该事件的所有线程均变
为可调度线程。
 人工重置
• 这类事件得到通知时,等待该事件的线程中只有一
个线程变为可调度线程。
Southeast University
东 南 大 学
56
http://www.njyangqs.com/
多核结构与程序设计
同步
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset
BOOL bInitialState,
LPCTSTR lpName );
 如果bManualReset被设置为:
人工重设事件
 FALSE: 自动重设事件
 TRUE:
 如果bInitialState被设置为:
事件开始于signaled状态
 FALSE: 事件开始于unsignaled状态
 TRUE:
Southeast University
东 南 大 学
57
http://www.njyangqs.com/
多核结构与程序设计
同步

事件的设置与复位
 设置一个事件到signaled状态
BOOL SetEvent( HANDLE hEvent );
 复位人工状态事件
BOOL ResetEvent( HANDLE hEvent );
 脉冲事件
BOOL PulseEvent( HANDLE hEvent );
Southeast University
东 南 大 学
58
http://www.njyangqs.com/
多核结构与程序设计
同步

使用事件
 考虑下面一个线程代码的例子:使用事件查找一
个线程
HANDLE hObj [2];
// 0 is event, 1 is thread
DWORD WINAPI threadFunc(LPVOID arg)
{
BOOL bFound = bigFind();
if (bFound)
{
SetEvent(hObj[0]);
// signal data was found
bigFound();
}
moreBigStuff();
return 0;
}
Southeast University
http://www.njyangqs.com/
59
东 南 大 学
多核结构与程序设计
同步

主函数
 考虑主程序的前一半:
...
hObj[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
hObj[1] = CreateThread(NULL,0,threadFunc,NULL,0,NULL);
/* Do some other work while thread executes search */
DWORD waitRet =
WaitForMultipleObjects(2, hObj, FALSE, INFINITE);
switch(waitRet) {
case WAIT_OBJECT_0:
printf ("found it!\n");
WaitForSingleObject(hObj[1], INFINITE) ;
case WAIT_OBJECT_0+1:
printf ("thread done\n");
break ;
default:
printf (“wait error: ret %u\n", waitRet);
break ;
}
.
.
.
Southeast University
东 南 大 学
// event signaled
// fall through
// thread signaled
60
http://www.njyangqs.com/
多核结构与程序设计
同步

主函数(继续)
 考虑主程序的后半部分:
...
hObj[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
hObj[1] = CreateThread(NULL,0,threadFunc,NULL,0,NULL);
/* Do some other work while thread executes search */
DWORD waitRet =
WaitForMultipleObjects(2, hObj, FALSE, INFINITE);
switch(waitRet) {
case WAIT_OBJECT_0:
printf ("found it!\n");
WaitForSingleObject(hObj[1], INFINITE) ;
case WAIT_OBJECT_0+1:
printf ("thread done\n");
break ;
default:
printf (“wait error: ret %u\n", waitRet);
break ;
}
...
Southeast University
东 南 大 学
61
// event signaled
// fall through
// thread signaled
http://www.njyangqs.com/