Windows线程同步

1.多线程程序容易出现的问题

多线程程序中,多个线程在申请唯一份资源时,存在一个隐患那就是重复使用。举一个例子,火车售票系统,一共100张票,用两个线程来模拟售票。

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    while (true)
    {
        if (gTickets > 0)
        {
            Sleep(10); //为了展现两个线程卖同一张票的情况,这里通过暂停线程实现。
            std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
    }

    return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

    while (true)
    {
        if (gTickets > 0)
        {
            Sleep(10); //为了展现两个线程卖同一张票的情况,这里通过暂停线程实现。
            std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
    }

    return 0;
}

运行结果:

出现这样的结果也很好解释,因为每个线程执行都有一个时间片,本例的情况是,当线程2刚好陷入if判断条件之后。线程2时间刚好用完,轮到线程1执行,当线程1执行完之后,时间片刚好轮到线程1,线程1就执行之后的语句就出现0号票的情况。


2.利用互斥对象实现线程的同步

互斥对象(mutex)属于内核对象,它能确保线程拥有对单个资源的互斥访问权限。互斥对象包含3部分,一个使用量,一个线程ID和一个计数器。其中ID用户识别系统中那个线程当前拥有互斥对象,计数器用户指明该线程拥有互斥对象的次数。

HANDLE WINAPI CreateMutex(
  __in          LPSECURITY_ATTRIBUTES lpMutexAttributes,
  __in          BOOL bInitialOwner,
  __in          LPCTSTR lpName
);
lpThreadAttributes 指向SECURITY_ATTRIBUTES结构指针,如果传递NULL,则表示线程使用默认的安全性。
bInitialOwner
指互斥对象初始的拥有者。如果为真,则创建这个互斥对象的线程获得对象的所有权。否则,没有线程获得所有权。

lpName
指定互斥对象的名称。如果为NULL,则创建一个匿名的互斥对象。否则,当调用成功,如果创建的互斥对象存在将返回该命名存在的互斥对象句柄。这时调用GetLastError函数将返回ERROR_ALREADY_EXISTS

当线程对共享资源访问结束后,应该释放该对象的所有权,让该对象处于已通知状态,这是需要调用ReleaseMutex函数。

BOOL WINAPI ReleaseMutex(
  __in          HANDLE hMutex
);

传入互斥对象的句柄,如果调用成功,返回非0值,否则返回0值。

线程必须主动请求共享对象的使用权才有可能获得所有权,这是通过WaitForSingleObject 函数实现,函数声明如下:

DWORD WINAPI WaitForSingleObject(
  __in          HANDLE hHandle,
  __in          DWORD dwMilliseconds
);
hHandle

所请求对象的句柄。一旦互斥对象处于有信号状态,则该函数就返回。否则将暂停线程的执行。

dwMilliseconds

指定等待的时间间隔,以毫秒为单位。如果时间间隔已过,函数立即返回。如果设置参数为0,那么函数测试对象状态立即返回;如果设置INFINITE,则函数一直等待,直到等待对象有信号状态才会返回。

利用互斥对象来实现线程同步:

#include <Windows.h>
#include <iostream>

DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

int gTickets(100);
HANDLE hMutex(NULL);
int main()
{
    HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    CloseHandle(hThread1);

    HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
    CloseHandle(hThread2);

    hMutex = CreateMutex(NULL, FALSE, NULL);

    Sleep(4000);

    return 0;
}

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    while (true)
    {
        //等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
        WaitForSingleObject(hMutex, INFINITE);

        if (gTickets > 0)
        {
            Sleep(10);
            std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);

    }

    return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

    while (true)
    {

        //等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
        WaitForSingleObject(hMutex, INFINITE);

        if (gTickets > 0)
        {
            Sleep(10);
            std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);

    }

    return 0;
}

实例2:

#include <Windows.h>
#include <iostream>

DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

int gTickets(100);
HANDLE hMutex(NULL);
int main()
{
    HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    CloseHandle(hThread1);

    HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
    CloseHandle(hThread2);

//     hMutex = CreateMutex(NULL, FALSE, NULL);
    hMutex = CreateMutex(NULL, TRUE, NULL);

    Sleep(4000);

    return 0;
}

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
    while (true)
    {
        ReleaseMutex(hMutex);
        //等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
        WaitForSingleObject(hMutex, INFINITE);
        if (gTickets > 0)
        {
            Sleep(10);
            std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);

    }

    return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

    while (true)
    {
        ReleaseMutex(hMutex);
        //等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
        WaitForSingleObject(hMutex, INFINITE);
        if (gTickets > 0)
        {
            Sleep(10);
            std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);

    }

    return 0;
}

我们得到的结果是没有打印任何信息。那是因为对互斥对象来说,谁拥有谁释放。所以当主线程创建互斥对象没有释放,那么线程1,线程2就不能释放互斥对象,因为互斥对象记录的线程ID是主线程的。所以线程1,线程2调用ReleaseMutex(hMutex)函数没有用,即子线程都在自己的时间片期间等待。


实例3:

只改动上面例子的mai函数,修改如下:

int main()
{
    HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    CloseHandle(hThread1);

    HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
    CloseHandle(hThread2);

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


    Sleep(4000);

    return 0;
}

这时你发现虽然主线程中你最后调用释放互斥对象函数,但是最终结果还是没有变。那是因为当你创建互斥对象并设置bInitialOwner参数为TRUE时,其互斥对象计数器加1,之后调用WaitForSingleObject(hMutex, INFINITE)函数此时的计数器为2。你调用一次释放互斥对象函数最终计数值还是1。所以主线程对互斥对象一直有信号,则子线程一直处于等待情况。


实例4:

#include <Windows.h>
#include <iostream>

DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

HANDLE hMutex(NULL);
int main()
{
    HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    CloseHandle(hThread1);

    HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
    CloseHandle(hThread2);

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

    Sleep(4000);

    return 0;
}

DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{

    WaitForSingleObject(hMutex, INFINITE);
    std::cout << "thread1 is running" << std::endl;

    return 0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

    WaitForSingleObject(hMutex, INFINITE);
    std::cout << "thread2 is running" << std::endl;

    return 0;
}


这种情况你会发现线程1,线程2都只执行一次。这是因为当操作系统一旦发现该线程已经终止,它就会自动将该线程拥有的互斥对象重新初始化,计数器为0,线程ID为0。


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值