`
luliangok
  • 浏览: 780461 次
文章分类
社区版块
存档分类
最新评论

同步机制及windows同步函数的使用

 
阅读更多

【转】同步机制及windows同步函数的使用

作者:simahao

原文链接:http://blog.csdn.net/simahao/archive/2005/07/15/425420.aspx

本篇文章适合比较熟悉多线程并且想学习线程同步的读者。

最近由于使用多线程,不可避免的要用到线程之间的同步,对一些常用的windows 中同步函数和机制有了一些初步的了解,并且写了一些小例子来验证,当然其中难免有错误和疏漏之处,希望高手能给我这个小鸟指出不足之处,非常感谢。

目录
一 临界区
二 互斥体
三 事件
四 信号量
五 附录

一 临界区

临界区的使用在线程同步中应该算是比较简单,说它简单还是说它同后面讲到的其它方法相比更容易理解。举个简单的例子:比如说有一个全局变量(公共资源)两个线程都会对它进行写操作和读操作,如果我们在这里不加以控制,会产生意想不到的结果。假设线程A正在把全局变量加1然后打印在屏幕上,但是这时切换到线程B,线程B又把全局变量加1然后又切换到线程A,这时候线程A打印的结果就不是程序想要的结果,也就产生了错误。解决的办法就是设置一个区域,让线程A在操纵全局变量的时候进行加锁,线程B如果想操纵这个全局变量就要等待线程A释放这个锁,这个也就是临界区的概念。


使用方法:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
...
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

/****************************************************************
*在使用临界区的时候要注意,每一个共享资源就有一个CRITICAL_SECTION
*如果要一次访问多个共享变量,各个线程要保证访问的顺序一致,如果不
*一致,很可能发生死锁。例如:
* thread one:
* EnterCriticalSection(&c1)
* EnterCriticalSection(&c2)
* ...
* Leave...
* Leave...
*
* thread two:
* EnterCriticalSection(&c2);
* EnterCriticalSection(&c1);
* ...
* Leave...
* Leave...
*这样的情况就会发生死锁,应该让线程2进入临界区的顺序同线程1相同
****************************************************************/

const int MAX_THREADNUMS = 4; //产生线程数目
CRITICAL_SECTION cs; //临界区
HANDLE event[MAX_THREADNUMS]; //保存createevent的返回handle
int critical_value = 0; //共享资源

UINT WINAPI ThreadFunc(void* arg)
{
int thread = (int)arg;
for (int i = 0; i < 5; i++)
{
EnterCriticalSection(&cs);
cout << "thread " << thread << " ";

critical_value++;
cout << "critical_value = " << critical_value << endl;
LeaveCriticalSection(&cs);
}
SetEvent(event[thread]);
return 1;
}

int main(int argc, char* argv[])
{
cout << "this is a critical_section test program" << endl;
HANDLE hThread;
UINT uThreadID;
DWORD dwWaitRet = 0;

InitializeCriticalSection(&cs);

for (int i = 0; i < MAX_THREADNUMS; i++)
{
event[i] = CreateEvent(NULL, TRUE, FALSE, "");
if (event[i] == NULL)
{
cout << "create event " << i << " failed with code: "
<< GetLastError() << endl;
continue;
}
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc,
(void*)i, 0, &uThreadID);
if (hThread == 0)
{
cout << "begin thread " << i << " failed with code: "
<< GetLastError() << endl;
continue;
}
CloseHandle(hThread);
}
//等待所有线程完成
dwWaitRet = WaitForMultipleObjects(MAX_THREADNUMS, event, TRUE, INFINITE);
switch(dwWaitRet)
{
case WAIT_OBJECT_0:

cout << "all the sub thread has exit!" << endl;
break;
default:
cout << "wait for all the thread failed with code:" << GetLastError() << endl;
break;
}

DeleteCriticalSection(&cs);
for (int k = 0; k < MAX_THREADNUMS; k++)
{
CloseHandle(event[k]);
}
return 0;
}

二 互斥体
windows api中提供了一个互斥体,功能上要比临界区强大。也许你要问,这个东东和临界区有什么区别,为什么强大?它们有以下几点不一致:
1.critical section是局部对象,而mutex是核心对象。因此像waitforsingleobject是不可以等待临界区的。
2.critical section是快速高效的,而mutex同其相比要慢很多
3.critical section使用范围是单一进程中的各个线程,而mutex由于可以有一个名字,因此它是可以应用于不同的进程,当然也可以应用于同一个进程中的不同线程。
4.critical section 无法检测到是否被某一个线程释放,而mutex在某一个线程结束之后会产生一个abandoned的信息。同时mutex只能被拥有它的线程释放。下面举两个应用mutex的例子,一个是程序只能运行一个实例,也就是说同一个程序如果已经运行了,就不能再运行了;另一个是关于非常经典的哲学家吃饭问题的例子。


程序运行单个实例:
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

//当输入s或者c时候结束程序
void PrintInfo(HANDLE& h, char t)
{
char c;
while (1)
{
cin >> c;
if (c == t)
{
ReleaseMutex(h);
CloseHandle(h);
break;
}
Sleep(100);
}
}
int main(int argc, char* argv[])
{
//创建mutex,当已经程序发现已经有这个mutex时候,就相当于openmutex
HANDLE hHandle = CreateMutex(NULL, FALSE, "mutex_test");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
cout << "you had run this program!" << endl;
cout << "input c to close this window" << endl;
PrintInfo(hHandle, 'c');
return 1;
}
cout << "program run!" << endl;
cout << "input s to exit program" <<endl;

PrintInfo(hHandle, 's');
return 1;
}


哲学家吃饭问题:

const int PHILOSOPHERS = 5; //哲学家人数
const int TIME_EATING = 50; //吃饭需要的时间 毫秒
HANDLE event[PHILOSOPHERS]; //主线程同工作线程保持同步的句柄数组
HANDLE mutex[PHILOSOPHERS]; //mutex数组,这里相当于公共资源筷子
CRITICAL_SECTION cs; //控制打印的临界区变量

UINT WINAPI ThreadFunc(void* arg)
{
int num = (int)arg;

DWORD ret = 0;
while (1)
{
ret = WaitForMultipleObjects(2, mutex, TRUE, 1000);
if (ret == WAIT_TIMEOUT)
{
Sleep(100);
continue;
}
EnterCriticalSection(&cs);
cout << "philosopher " << num << " eatting" << endl;
LeaveCriticalSection(&cs);
Sleep(TIME_EATING);
break;
}
//设置时间为有信号
SetEvent(event[num]);
return 1;
}
int main(int argc, char* argv[])
{
HANDLE hThread;
InitializeCriticalSection(&cs);
//循环建立线程
for (int i = 0; i < PHILOSOPHERS; i++)
{
mutex[i] = CreateMutex(NULL, FALSE, "");
event[i] = CreateEvent(NULL, TRUE, FALSE, "");
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)i, 0, NULL);
if (hThread == 0)
{
cout << "create thread " << i << "failed with code: "
<< GetLastError() << endl;
DeleteCriticalSection(&cs);
return -1;
}
CloseHandle(hThread);
}

//等待所有的哲学家吃饭结束
DWORD ret = WaitForMultipleObjects(PHILOSOPHERS, event, TRUE, INFINITE);
if (ret == WAIT_OBJECT_0)
{
cout << "all the philosophers had a dinner!" << endl;
}
else
{
cout << "WaitForMultipleObjects failed with code: " << GetLastError() << endl;
}
DeleteCriticalSection(&cs);
for (int j = 0; j < PHILOSOPHERS; j++)
{
CloseHandle(mutex[j]);
}
return 1;
}

三 事件
事件对象的特点是它可以应用在重叠I/O(overlapped I/0)上,比如说socket编程中有两种模型,一种是重叠I/0,一种是完成端口都是可以使用事件同步。它也是核心对象,因此可以被waitforsingleobje这些函数等待;事件可以有名字,因此可以被其他进程开启。我在前几个例子当中其实已经使用到event了,在这里就不多说了,可以参考前一个例子。

四 信号量
semaphore的概念理解起来可能要比mutex还难, 我先简单说一下创建信号量的函数,因为我在开始使用的时候没有很快弄清楚,可能现在还有理解不对的地方,如果有错误还是请大侠多多指教。
CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
LONG lInitialCount, // initial count
LONG lMaximumCount,// maximum count
LPCTSTR lpName// object name
)
第一个参数是安全性,可以使用默认的安全性选项NULL;第二个和第三个参数是两个long型的数值,它们表示什么含义呢?lMaxinumCount表示信号量的最大值,必须要大于零。比如是5就表示可以有5个进程或者线程使用,如果第六个进程或者线程想使用的话就必须进入等待队列等待有进程或者线程释放资源。lInitalCount表示信号量的初始值,应该大于或者等于零小于等于lMaximumCount。如果lInitialCount = 0 && lMaximumCount == 5,那么就表示当前资源已经全部被使用,如果再有进程或者线程想使用的话,信号量就会变成-1,该进程或者线程进入等待队列,直到有进程或者线程执行ReleaseMutex;如果lInitialCount = 5 && lMaximumCount == 5,那么就表示现在信号量可以被进程或者线程使用5次,再之后就要进行等待;如果InitialCount = 2 && MaximumCount == 5这样的用法不太常见,表示还可以调用两次CreateSemaphore或者OpenSemaphore,再调用的话就要进入等待状态。最后一个参数表示这个信号量的名字,这样就可以跨进程的时候通过这个名字OpenSemaphore。说了这么多了,不知道说明白没有~


看个例子,popo现在好像在本机只能运行三个实例,我们在前面说的mutex可以让程序只是运行一个实例,下面我通过信号量机制让程序像popo一样运行三个实例。

#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;

const int MAX_RUNNUM = 3; //最多运行实例个数
void PrintInfo()
{
char c;
cout << "run program" << endl;
cout << "input s to exit program!" << endl;
while (1)
{
cin >> c;
if (c == 's')
{
break;
}
Sleep(10);
}
}
int main(int argc, char* argv[])
{

HANDLE hSe = CreateSemaphore(NULL, MAX_RUNNUM, MAX_RUNNUM, "semaphore_test");
DWORD ret = 0;

if (hSe == NULL)
{
cout << "createsemaphore failed with code: " << GetLastError() << endl;
return -1;
}


ret = WaitForSingleObject(hSe, 1000);
if (ret == WAIT_TIMEOUT)
{
cout << "you have runned " << MAX_RUNNUM << " program!" << endl;
ret = WaitForSingleObject(hSe, INFINITE);
}

PrintInfo();
ReleaseSemaphore(hSe, 1, NULL);
CloseHandle(hSe);
return 0;
}

附录:
核心对象
Change notification
Console input
Event
Job
Mutex
Process
Semaphore
Thread
Waitable timer

分享到:
评论

相关推荐

    线程同步机制代码,用c++写的,:使用Windows互斥信号量操作函数和同步机制的Peterson,实现进程互斥和同步

    小实验三:根据同步机制的Peterson软件解决方案尝试自己编程实现线程同步机制和用于上述线程并发问题的解决,并基于程序运行时间长短将其与基于Windows互斥信号量的线程同步机制的效率展开比较。 实验要求:线程主体...

    精通WindowsAPI 函数 接口 编程实例

    5.3.3 使用全局和局部函数分配和释放内存、改变内存块属性 137 5.4 虚拟内存管理 138 5.4.1 虚拟地址空间与内存分页 139 5.4.2 分配和释放可读可写的虚拟内存页面 139 5.4.3 修改内存页面状态和保护属性、...

    精通Windows.API-函数、接口、编程实例.pdf

    5.3.3 使用全局和局部函数分配和释放内存、改变内存块属性 137 5.4 虚拟内存管理 138 5.4.1 虚拟地址空间与内存分页 139 5.4.2 分配和释放可读可写的虚拟内存页面 139 5.4.3 修改内存页面状态和保护属性、...

    操作形同实验——进程同步和互斥

    操作形同实验——进程同步和互斥 (1) 通过编写程序实现进程同步和互斥,掌握有关进程(线程)同步与互斥的原理,以及解决进程(线程)同步和...(3) 学习使用Windows2000/XP中基本的同步对象,掌握相应的API函数。

    TCP异步同步机制

    自己封装的TCP通信的代码,调用相关函数连接并提供回调函数,当有网络数据会自动回调,就像Windows Message原理差不多~

    WindowsAPI参考手册

    Microsoft Win32 API 函数按照功能划分为窗口处理、设备上下文——DC 、绘图函数、 位图和图标、菜单处理、文件处理、同步、处理文本和字体、硬件和系统、Windows 消息、 进程和线程。 本书按功能对Win32 API 函数...

    用WIN32API函数实现Windows下的串行通讯

    其二是调用Windows的API函数,这种方法可以清楚地掌握串口通信的机制,并且自由灵活。本文我们只介绍API串口通信部分。  串口的操作可以有两种操作方式:同步操作方式和重叠操作方式(又称为异步操作方式)。同步...

    API之网络函数---整理网络函数及功能

    CreateScalableFontResource 为一种TureType字体创建一个资源文件,以便能用API函数AddFontResource将其加入Windows系统 DrawText 将文本描绘到指定的矩形中 DrawTextEx 与DrawText相似,只是加入了更多的功能 ...

    windows驱动开发技术详解-part2

     5.4.5 关于运行时函数使用的注意事项  5.4.6 实验  5.5 使用C++特性分配内存  5.6 其他  5.6.1 数据类型  5.6.2 返回状态值  5.6.3 检查内存可用性  5.6.4 结构化异常处理(try-except块)  5.6.5...

    vc++ 线程同步与异步套接字编程实例

    vc++ 线程同步与异步套接字编程实例,Windows套接字在两种模式下执行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)。而在非...

    Windows编程循序渐进源码

    本书设计了大量的实例演示Windows应用程序开发过程中相关技术,分为3个部分: (1)软件设计基础篇。  第1章,软件开发起步:编写第一个... 第19章,模块注入与函数挂接技术:介绍模块注入及函数挂接技术及其防御。

Global site tag (gtag.js) - Google Analytics