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

  操作系统为每一个运行线程安排一定的CPU时间 —— 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,多个线程不断地切换运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。 
  单cpu计算机一个时间只能运行一个线程,如果计算机拥有多个CPU,线程就能真正意义上同时运行了。 
  windows平台下,创建线程可以使用windows api 函数CreateThread来实现,函数声明是:

WINBASEAPI
HANDLE
WINAPI
CreateThread(
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 <windows.h>

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

#include <iostream.h>

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

//问题程序

#include <windows.h>
#include <iostream.h> 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 : "<<tickets--<<endl;
}
else
break;
}
return 0;
} DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
)
{
while(TRUE)
{
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
}
else
break;
}
return 0;
}

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

执行结果

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

  • 互斥对象 
    特征: 
      互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 
      互斥对象包含一个使用数量,一个线程ID和一个计数器。 
      ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。 
      采用互斥对象进行多线程同步的正确例子如下:
#include <windows.h>
#include <iostream.h> 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 : "<<tickets--<<endl;
}
else
break;
//释放指定互斥对象的所有权,操作系统将互斥对象的线程id被置为0,互斥对象变为已通知状态,线程2就能请求到互斥对象
ReleaseMutex(hMutex);
}
return 0;
} DWORD WINAPI Fun2Proc(
LPVOID lpParameter // thread data
)
{
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"thread2 sell ticket : "<<tickets--<<endl;
}
else
break;
ReleaseMutex(hMutex);
}
return 0;
}
  • 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"<<endl;
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
waitforsingleobject(hmutex,infinite);
cout<<"thread2 is running"<<endl;
return 0;
}

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

    "thread1 is running
"thread2 is running

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

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

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

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

【转】windows平台多线程同步之Mutex的应用的更多相关文章

  1. C++多线程同步之Mutex(互斥量)

    原文链接: http://blog.csdn.net/olansefengye1/article/details/53086141 一.互斥量Mutex同步多线程 1.Win32平台 相关函数和头文件 ...

  2. c# 多线程同步之Mutex

    说起Mutex,它的中文名字叫互斥体.它是WaitHandle家族成员之一,前面有一篇介绍过WaitHandle的家族成员构成.那么Mutex有什么作用呢?它是怎么使用的? 我们先来看看它的使用场景一 ...

  3. Windows多线程同步系列之一-----互斥对象

    多线程同步之互斥对象 作者:vpoet mail:vpoet_sir@163.com   对卖票问题进行线程间同步,本文将在上文的基础上,使用互斥对象对线程进行同步. 首先看看windows API ...

  4. windows多线程同步

    概述 任何单个应用程序都不能完全使该处理器达到满负荷.当一个线程遇到较长等待时间事件时,同步多线程还允许另一线程中的指令使用所有执行单元.例如,当一个线程发生高速缓存不命中,另一个线程可以继续执行.同 ...

  5. 基于Windows平台的Python多线程及多进程学习小结

    python多线程及多进程对于不同平台有不同的工具(platform-specific tools),如os.fork仅在Unix上可用,而windows不可用,该文仅针对windows平台可用的工具 ...

  6. Windows多线程同步系列之二-----关键区

    关键区对象为:CRITICAL_SECTION 当某个线程进入关键区之后,其他线程将阻塞等待,知道该线程释放关键区的拥有权. 关键区同步主要有以下几个API 初始化关键区对象,无返回值,传入一个关键区 ...

  7. 总结windows多线程同步互斥

    windows多线程同步互斥--总结 我的windows多线程系列文章: windows多线程--原子操作 windows多线程同步--事件 windows多线程同步--互斥量 windows多线程同 ...

  8. windows多线程同步互斥--总结

    我的windows多线程系列文章: windows多线程--原子操作 windows多线程同步--事件 windows多线程同步--互斥量 windows多线程同步--临界区 windows多线程同步 ...

  9. windows多线程同步--临界区

    推荐参考博客:秒杀多线程第五篇 经典线程同步 关键段CS   关于临界区的观念,一般操作系统书上面都有. 适用范围:它只能同步一个进程中的线程,不能跨进程同步.一般用它来做单个进程内的代码快同步,效率 ...

随机推荐

  1. Linux系统中最好用的截图软件介绍

    当我的主力操作系统从 Windows 转换到 Ubuntu 的时候,首要考虑的就是屏幕截图工具的可用性.尽管使用默认的键盘快捷键也可以获取屏幕截图,但如果使用屏幕截图工具,可以更方便地对屏幕截图进行编 ...

  2. [Algorithm] Count occurrences of a number in a sorted array with duplicates using Binary Search

    Let's say we are going to find out number of occurrences of a number in a sorted array using binary ...

  3. 35个让人惊讶的CSS3动画效果

    1. Pure CSS Coke Can 2. Colorful Clock 3. jQuery DJ Hero 4. Animated Pricing Column 5. Slick jQuery  ...

  4. java interface 默认值

    /* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Inc. or th ...

  5. background-size中contain和cover中的数学公式

    background-size的contain和cover是怎么用的,大家应该都明白.但是里面也有一些有趣的数学关系. 基本概念 上面就是我们对于 rimage (图片宽高比).rviewport ( ...

  6. iOS开发技巧 - 使用UISlider来调整值的范围

    (Swift) import UIKit class ViewController: UIViewController { var slider: UISlider! func sliderValue ...

  7. JAVA的PreparedStatement和addBatch()方法

    本文介绍两个内容,为什么使用PreparedStatement的addBatch()方法?以及使用PreparedStatement的好处. 一.addBatch使用方法 昨天用JAVA做了一个导表的 ...

  8. QT 截取屏幕的实现

    QPixmap提供了两个函数 grabWidget 和 grabWindow 可以将屏幕上的窗体存成一个 QPixmap 格式的图片,很容易再将 QPixmap 存成文件.函数使用很简单,两行代码就可 ...

  9. Vue为什么没有templateUrl

    Why Vue.js doesn't support templateURL Vue.js为什么不支持templateUrl模式 原因 templateUrl使用ajax的方式在运行时加载templa ...

  10. WIN下Git GUI 教程

    现在很多都有git来托管项目或者来查找资料,但是看起来操作不是很方便,现在由于win下可以直接使用git gui,让使用git变得方便,当然这只是针对日常简单的使用,如果想详细的使用,可以去参考廖学峰 ...