windows平台多线程同步实现之Mutex对象的使用

windows平台多线程同步实现之MutexMutex对象的使用

  • 前言

    线程组成

    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;
}

执行结果

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

  • 互斥对象的释放问题

    ??如果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进行介绍和解析,敬请关注。文中如有谬误,还望不吝赐教。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-06 02:35:21

windows平台多线程同步实现之Mutex对象的使用的相关文章

windows下多线程同步(利用事件对象,互斥对象,关键代码段)实现

一:利用事件实现线程同步 1.createthread函数的用法 hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ; HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAdd

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

关键区对象为:CRITICAL_SECTION 当某个线程进入关键区之后,其他线程将 阻塞等待,知道该线程释放关键区的拥有权. 关键区同步主要有以下几个API 初始化关键区对象,无返回值,传入一个关键区对象的指针. 该函数在使用完关键区之后删除关键区对象,无返回值,同样传入一个关键区对象指针 该函数为进入关键区函数,传入的参数为关键区对象指针,无返回值.如果另外一个线程正拥有关键区的所有 权,那么该函数将阻塞,直到其他线程释放关键区的所有权之后,该函数才能继续执行进入关键区. 该函数与Enter

多线程同步之互斥对象

多线程同步之互斥对象 作者:vpoet mail:[email protected] 在http://blog.csdn.net/u013018721/article/details/46637215一文中介绍了使用临界区 对卖票问题进行线程间同步,本文将在上文的基础上,使用互斥对象对线程进行同步. 首先看看windows API 该函数创建一个命名或者不命名的互斥对象 lpMutexAttributes [in] Pointer to a SECURITY_ATTRIBUTES structu

windows多线程同步

概述 任何单个应用程序都不能完全使该处理器达到满负荷.当一个线程遇到较长等待时间事件时,同步多线程还允许另一线程中的指令使用所有执行单元.例如,当一个线程发生高速缓存不命中,另一个线程可以继续执行.同步多线程是 POWER5? 和 POWER6? 处理器的功能,可与共享处理器配合使用. SMT 对于商业事务处理负载的性能优化可达30%.在更加注重系统的整体吞吐量而非单独线程的吞吐量时,SMT 是一个很好地选择. 但是并非所有的应用都能通过SMT 取得性能优化.那些性能受到执行单元限制的应用,或者

转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

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

python多线程及多进程对于不同平台有不同的工具(platform-specific tools),如os.fork仅在Unix上可用,而windows不可用,该文仅针对windows平台可用的工具进行总结. 1.多线程 单线程中,如果某一任务(代码块)是long-time running的,则必须等待该任务(代码块)结束,才可以对下一个任务进行操作,为解决long-time 任务的block问题,可将创建多个线程,间隔选择多线程进行操作.python 中多线程常用的库为_thread,thr

Delphi多线程编程(12)--多线程同步之Semaphore(信号对象)

转载自:万一的博客 之前已经有了两种线程同步的方法: CriticalSection(临界区)和Mutex(互斥)吗,这两种同步方法差不多,只是作用域不同 CriticalSection类似于只有一个蹲位的公共厕所,只能一个个地进 Mutex 对象类似于接力赛中的接力棒,某一时刻只能有一个人持有,谁拿着谁跑 什么是Semaphore(信号或叫信号量)呢? 譬如到银行办业务.或者到车站买票,原来只有一个服务员,不管有多少人排队等候,业务只能一个个地来 假如增加业务窗口,可以同时受理几个业务呢? 这

多线程同步与并发访问共享资源工具—Lock、Monitor、Mutex、Semaphore

"线程同步"的含义 当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行"线程同步(thread synchronization)". 线程同步的道理虽然简单,但却是给多线程开发带来复杂性的根源之一.当线程同步不好时,有可能会出现一种特殊的情形--死锁(Dead Lock). "死锁"的含义 死锁表示系统进入了一个僵化状态,所有线程都没有执行完毕,但却谁也没法继续