博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】windows平台多线程同步之Mutex的应用
阅读量:6969 次
发布时间:2019-06-27

本文共 4237 字,大约阅读时间需要 14 分钟。

  • 线程组成: 
    1. 线程的内核对象,操作系统用来管理该线程的数据结构。
    2. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

  为每一个运行线程安排一定的CPU时间 —— 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,多个线程不断地切换运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。 

  单cpu计算机一个时间只能运行一个线程,如果计算机拥有多个CPU,线程就能真正意义上同时运行了。 
  windows平台下,创建线程可以使用windows api 函数CreateThread来实现,函数声明是:

WINBASEAPIHANDLEWINAPICreateThread(    LPSECURITY_ATTRIBUTES lpThreadAttributes,    DWORD dwStackSize,    LPTHREAD_START_ROUTINE lpStartAddress,    LPVOID lpParameter,    DWORD dwCreationFlags,    LPDWORD lpThreadId    );

参数说明:

lpThreadAttributes 线程安全性,使用缺省安全性,一般缺省null
dwStackSize 堆栈大小,0为缺省大小
lpStartAddress 线程要执行的函数指针,即入口函数
lpParameter 线程参数
dwCreationFlags 线程标记,如为0,则创建后立即运行
lpThreadId LPDWORD为返回值类型,一般传递地址去接收线程的标识符,一般设为null

因为要使用windows api函数,所以包含:

#include 

另外需要标准输入输出函数,所以包含:

#include 

- 问题引出   以多个售票窗口卖同一张火车票为例,定义一个全局的票数tickets,用两个线程来执行卖票,两个线程访问同一个变量tickets,先看一个不正确的写法:

//问题程序#include 
#include
DWORD WINAPI Fun1Proc( LPVOID lpParameter );DWORD WINAPI Fun2Proc( LPVOID lpParameter );int tickets=100; void main() { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); system("pause"); } DWORD WINAPI Fun1Proc( LPVOID lpParameter ) { while(TRUE) { if(tickets>0) { Sleep(1);//假定为卖票需要花费的时间 cout<<"thread1 sell ticket : "<
<
0) { Sleep(1); cout<<"thread2 sell ticket : "<
<

 

  线程中sleep(1);表名该线程放弃执行的权利,操作系统会选择另外的线程进行执行。所以执行结果是:

执行结果

 

  可以看见程序不是按照预期的效果执行的,tickets的改变是混乱的。所以两个线程访问同一块资源时,需要考虑线程同步问题,即其中一个线程操作改资源时,其他线程不能访问该资源,只能等待,该线程执行结束之后,其他线程才能对该资源进行访问。 
  一般采用互斥对象来实现线程的同步。

  • 互斥对象 
    特征: 
      互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 
      互斥对象包含一个使用数量,一个线程ID和一个计数器。 
      ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。 
      采用互斥对象进行多线程同步的正确例子如下:
#include 
#include
DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data);DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data);int index=0; int tickets=100; HANDLE hMutex; void main() { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); //创建一个匿名的互斥对象,且为有信号状态, hMutex=CreateMutex(NULL,FALSE,NULL); system("pause"); } DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ) { while(TRUE) { //等待互斥对象的信号,INFINITE表示一直等待,对之后的代码进行保护 WaitForSingleObject(hMutex,INFINITE); if(tickets>0) { Sleep(1); cout<<"thread1 sell ticket : "<
<
0) { Sleep(1); cout<<"thread2 sell ticket : "<
<
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

执行结果

 

  通过测试可知,以上互斥对象的引入可以很好的解决线程间访问资源同步的问题。关于互斥对象,还有以下几个问题需要说明。

  • 互斥对象的释放问题 
      如果main中
hMutex=CreateMutex(NULL,TRUE,NULL);

 

  子线程中:

while(TRUE)    {        ReleaseMutex(hMutex);//无效        //等待互斥对象的信号,INFINITE表示一直等待,对之后的代码进行保护        WaitForSingleObject(hMutex,INFINITE); }

 

  如果CreateMutex第二个参数为true,则表示主线程拥有该互斥对象,操作系统将互斥对象的线程id设为主线程的线程id,如果主线程不释放,则子线程会一直等待,此时子线程也没有权利进行释放,所以使用互斥对象的原则是:谁拥有互斥对象,谁释放互斥对象。

  另外,如果main中

hMutex=CreateMutex(NULL,TRUE,NULL);

  并且再次请求互斥对象:

WaitForSingleObject(hMutex,INFINITE);

  并调用一次释放互斥对象:

ReleaseMutex(hMutex);

 

  此时子线程依然是等待状态,得不到互斥对象的使用权,原因是: 

  CreateMutex(NULL,TRUE,NULL)由于第二个参数为true,主线程拥有互斥对象的使用权,互斥对象内部计数器加1,再次调用WaitForSingleObject请求互斥对象时,内部计数器又加1,计数器是记录线程拥有互斥对象的次数,而只释放ReleaseMutex了一次,互斥对象依然被占用,所以子线程得不到使用权。 
  因此正确的写法是:

hMutex=CreateMutex(NULL,TRUE,NULL);    WaitForSingleObject(hMutex,INFINITE);    ReleaseMutex(hMutex);    ReleaseMutex(hMutex);

  如果多次请求互斥对象,就应该多次释放互斥对象。

  再看这样一种情况,线程中没有释放互斥对象的拥有权:

DWORD WINAPI Fun1Proc(LPVOID lpParameter)    {        waitforsingleobject(hmutex,infinite);        cout<<"thread1 is running"<

  此时执行依然能够得到输出:

"thread1 is running    "thread2 is running

 

  这是因为:如果线程退出时没有释放互斥对象,操作系统在销毁线程时会自动将线程占用的互斥对象的信息清除,计数器归零,这样其他线程(thread2 )就能申请到互斥对象使用权。

  • 创建命名互斥对象
hMutex=CreateMutex(NULL,TRUE,"myApp");    if(hMutex)    {        if(ERROR_ALREADY_EXISTS==GetLastError()) { cout<<"已经有一个相同应用程序在运行!"<

  命名互斥对象的一种应用是:通过命名互斥对象,可以保证当前只有一个应用程序实例在运行。

  以上是关于windows平台下多线程同步相关的互斥对象的使用问题,之后将对线程同步的事件对象Event进行介绍和解析,敬请关注。文中如有谬误,还望不吝赐教。

你可能感兴趣的文章
nginx在linux环境下安装
查看>>
笨兔兔的故事——带你了解Ubuntu,了解Linux 第九章 邻居
查看>>
ubuntu中安装hadoop集群
查看>>
14_02_Linux系统启动流程详解之二 内核及init
查看>>
【MongoDB for Java】Java操作MongoDB
查看>>
Android Material风格的应用(一)--AppBar TabLayout
查看>>
美团外卖客户端高可用建设体系
查看>>
TR069协议简介
查看>>
HDFS文件写入
查看>>
oajinit下载路径
查看>>
Windows 2012 Hyper-V Step by Step (四) 创建iSCSI映射
查看>>
EasyUI的渣性能
查看>>
puppet自动化运维之cron资源
查看>>
Linux系统下MongoDB的简单安装与基本操作
查看>>
jquery全选,jquery全不选,jquery反选 .
查看>>
Extjs4——实现动态的grid(即实现数据库表数据的显示)
查看>>
nginx ,varnish测试
查看>>
Iptables2
查看>>
cocos2d-x lua分支加载ccbi文件
查看>>
kafka & zookeeper 4/12/2016
查看>>