几种多线程同步方式总结

互斥对象

互斥对象:互斥对象(Mutex)属于内核对象,它能确保线程拥有对单个资源的互斥范围权利,即线程A正在拥有资源R,线程B恰好也要使用资源R,则线程B会等到线程A使用完资源后,才去使用资源R。

互斥对象包含一个使用数量、一个线程ID和一个计数器。线程ID标识系统中哪个线程拥有该互斥对象,计数器用于指明该线程拥有互斥对象的次数。

功能:创建或打开一个命名或未命名的互斥对象。

互斥对象(mutex)属于内核对象(因为是内核对象,所以其参数列表中有安全描述指针),它能够确保线程拥有对单个资源的互斥访问权。

创建互斥对象

HANDLE CreateMutexA(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL                  bInitialOwner,
  LPCSTR                lpName
);

其中:

lpMutexAttributes:指向SECURITY_ATTRIBUTES结构的指针 。如果此参数为NULL,则子进程不能继承句柄,让互斥对象使用默认的安全性。

bInitialOwner:
Bool值,指定互斥对象初始的拥有者。

指定互斥对象初始的拥有者,TRUE:创建者对象的线程获得该对象的使用权,内部计数器加1否则,该线程不获得互斥对象的所有权

当为true时,则创建该互斥对象(执行CreateMutexA该语句的)线程拥有该互斥对象所有权;【比如Main函数执行该语句,则主函数拥有该互斥对象,其他线程即使执行WaitForSingleObject也没法获得该互斥对象,除非主线程自己执行ReleaseMutex函数释放对该互斥对象的拥有权】;

为false时,表明当前没有没有线程拥有这个互斥对象,故OS会将该互斥对象设置为有信号状态,其他线程都可以执行WaitForSingleObject来获取该互斥对象所有权。)

lpName:指定互斥对象的名称。该名称限制为MAX_PATH 个字符。名称比较区分大小写。如果为NULL,则创建的是一个匿名的互斥对象。

释放互斥对象

BOOL ReleaseMutex(
  HANDLE hMutex
);

功能:释放指定对象的所有权。

当我们对共享资源访问后,我们需要释放该对象的所有权,让这个互斥对象处于已通知状态;此时可以调用ReleaseMutex函数,调用该函数相当于互斥对象的计数器减1

hMutex 需要释放的互斥对象的句柄

当前线程调用了该函数,则其他线程就有机会获得该对象的所有权,从而获得共享资源的访问;

互斥对象实现线程同步

初始状态没有线程拥有该互斥对象时:

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);
DWORD WINAPI Func2Proc(LPVOID lpParam);
int index = 0;
int tickets = 100;
HANDLE hMutex;
int main()
{

    HANDLE hThread1;
    HANDLE hThread2;
    hMutex = CreateMutex(NULL, FALSE, NULL);//CreateMutex创建一个互斥对象。第二个参数为FALSE,表明当前没有没有线程拥有这个互斥对象,故OS会将该互斥对象设置为有信号状态

    hThread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    hThread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(hThread1);//实际上调用CloseHandle并没有中止新创建的线程,只是表示在主线程中对创建的线程的引用不感兴趣
    CloseHandle(hThread2);
    //创建一个匿名的互斥对象
    Sleep(4000);
    cout << "main thread is running" << endl;
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权,线程1会一直等待,除非所请求的对象处于有信号状态,该函数才会返回,线程才能继续往下执行,才能执行受保护的代码
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread1 sell tickets:" << tickets-- << endl;
            Sleep(10);//线程1暂停执行,OS选择线程2开始执行,进入线程2的while语句发现互斥对象被线程1持有,属于无信号状态,故等待其成为有信号状态。
            //线程1睡醒后继续执行,直到执行ReleaseMutex释放所有权,线程2才获取互斥对象
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权,此时互斥对象处于有信号状态,其他线程有机会获得该对象的所有权
    }
    return 0;
}
DWORD WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread2 sell tickets:" << tickets-- << endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}
View Code

 几种多线程同步方式总结

初始状态主线程拥有该互斥对象所有权时:

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);
DWORD WINAPI Func2Proc(LPVOID lpParam);
int index = 0;
int tickets = 100;
HANDLE hMutex;
int main()
{

    HANDLE hThread1;
    HANDLE hThread2;
    hMutex = CreateMutex(NULL, TRUE, NULL);//CreateMutex创建一个互斥对象。第二个参数为TRUE,表明主线程拥有这个互斥对象,其他线程无法获取对该互斥对象的所有权。
    ReleaseMutex(hMutex);//主线程释放对该互斥对象的所有权。(对互斥对象来说,谁拥有,谁释放)
    hThread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    hThread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(hThread1);//实际上调用CloseHandle并没有中止新创建的线程,只是表示在主线程中对创建的线程的引用不感兴趣
    CloseHandle(hThread2);
    //创建一个匿名的互斥对象
    Sleep(4000);
    cout << "main thread is running" << endl;
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权,线程1会一直等待,除非所请求的对象处于有信号状态,该函数才会返回,线程才能继续往下执行,才能执行受保护的代码
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread1 sell tickets:" << tickets-- << endl;
            Sleep(10);//线程1暂停执行,OS选择线程2开始执行,进入线程2的while语句发现互斥对象被线程1持有,属于无信号状态,故等待其成为有信号状态。
            //线程1睡醒后继续执行,直到执行ReleaseMutex释放所有权,线程2才获取互斥对象
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权,此时互斥对象处于有信号状态,其他线程有机会获得该对象的所有权
    }
    return 0;
}
DWORD WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread2 sell tickets:" << tickets-- << endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}
View Code
DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

几种多线程同步方式总结

 

 在hMutex = CreateMutex(NULL, TRUE, NULL);语句后执行ReleaseMutex(hMutex);//主线程释放对该互斥对象的所有权。(对互斥对象来说,谁拥有,谁释放)。

结果与截图1一致(交替打印票数)。

 初始状态主线程拥有该互斥对象所有权,且主线程执行WaitForSingleObject再次请求互斥对象的情况:

主线程需要两次释放对互斥对象的请求权才能执行交替打印。

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);
DWORD WINAPI Func2Proc(LPVOID lpParam);
int index = 0;
int tickets = 100;
HANDLE hMutex;
int main()
{

    HANDLE hThread1;
    HANDLE hThread2;
    hMutex = CreateMutex(NULL, TRUE, NULL);//CreateMutex创建一个互斥对象。第二个参数为TRUE,表明主线程拥有这个互斥对象,其他线程无法获取对该互斥对象的所有权。
    WaitForSingleObject(hMutex, INFINITE);//若请求的线程ID和互斥对象内部的线程ID是相同的,即使此时此互斥对象处于未通知状态,该线程还可以请求到该互斥对象的所有权,计数器则会再加1。所以此时执行了CreateMutex和WaitForSingleObject后,互斥对象的计数器为2,需要释放两次
    ReleaseMutex(hMutex);//主线程释放对该互斥对象的所有权。(对互斥对象来说,谁拥有,谁释放)
    ReleaseMutex(hMutex);
    hThread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    hThread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(hThread1);//实际上调用CloseHandle并没有中止新创建的线程,只是表示在主线程中对创建的线程的引用不感兴趣
    CloseHandle(hThread2);
    //创建一个匿名的互斥对象
    Sleep(4000);
    cout << "main thread is running" << endl;
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权,线程1会一直等待,除非所请求的对象处于有信号状态,该函数才会返回,线程才能继续往下执行,才能执行受保护的代码
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread1 sell tickets:" << tickets-- << endl;
            Sleep(10);//线程1暂停执行,OS选择线程2开始执行,进入线程2的while语句发现互斥对象被线程1持有,属于无信号状态,故等待其成为有信号状态。
            //线程1睡醒后继续执行,直到执行ReleaseMutex释放所有权,线程2才获取互斥对象
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);//释放当前线程对互斥对象的所有权,此时互斥对象处于有信号状态,其他线程有机会获得该对象的所有权
    }
    return 0;
}
DWORD WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求互斥对象的所有权
        WaitForSingleObject(hMutex, INFINITE);
        if (tickets > 0)
        {
            cout << "thread2 sell tickets:" << tickets-- << endl;
        }
        else
        {
            break;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}
View Code

 WaitForSingleObject

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

功能:等待指定对象处于信号状态或超时间隔结束。

hHandle为所请求的对象的句柄,该函数可以等待的对象包括:

  • Change notification
  • Console input
  • Event
  • Memory resource notification
  • Mutex
  • Process
  • Semaphore
  • Thread
  • Waitable timer

dwMilliseconds:指定等待的时间间隔。

如果该参数为0表示测试该对象的状态就立即返回,不关心线程是否获得所有权,相当于没有进行线程同步处理。

如果该参数为INFINITE表示函数会一直等待,直到等待的对象处于有信号状态才会返回,这里能保证资源的独占性。

 返回类型

有三种返回类型:

WAIT_OBJECT_0, 表示等待的对象有信号(对线程来说,表示执行结束);

 WAIT_TIMEOUT, 表示等待指定时间内,对象一直没有信号(线程没执行完);

WAIT_ABANDONED 表示对象有信号,但还是不能执行  一般是因为未获取到锁或其他原因

 线程若要获得互斥对象的所有权,则必须主动发出请求才能获得该互斥对象的所有权;获得对象的所有权,可以调用WaitForSingleObject函数来实现,若成功获得互斥对象的执行权,该互斥对象的计数器则增加1。

一旦互斥对象处于有信号状态,该函数就返回。如果该互斥对象一致处于无状态对象,函数会一致等待,该线程暂停执行。

事件

事件对象也属于内核对象,它包含以下三个成员:

使用计数;

用于指明该事件 是一个自动重置的事件还是人工重置的事件的布尔值;

用于指明该事件处于已通知状态还是未通知状态的布尔值。

事件对象有两种不同的类型:

人工重置事件对象:当人工重置对象得到通知时,等待该事件的所有线程均为可调度状态。

自动重置对象:当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。

相关API函数:

创建事件对象

HANDLE CreateEventA(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCSTR                lpName
);

lpEventAttributes

指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则子进程不能继承句柄

bManualReset

Bool类型,指定创建的是人工重置事件对象,还是自动重置事件对象。如果此参数为TRUE,该函数将创建一个人工重置事件对象,这需要使用 ResetEvent函数将事件状态设置为无信号。如果此参数为FALSE,则该函数创建一个自动重置事件对象,当线程等到该对象所有权之后,系统会自动将该对象设置为无信号状态。

bInitialState
指定事件对象的初始状态。为true,则对象初始是有信号状态,否则是无信号状态。

lpName
指定事件对象的名称。如果为NULL,则创建一个匿名的事件对象。

设置事件对象状态

BOOL SetEvent(
  HANDLE hEvent  //事件对象的句柄
);

功能:把指定事件对象设置为有信号状态。

重置事件对象状态

BOOL ResetEvent(
  HANDLE hEvent //事件对象句柄
);

功能:把指定的事件对象设置为无信号状态。成功返回非0,失败返回0

事件对象实现线程同步

当使用初始状态为无信号人工重置的事件对象时:

#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);//LPVOID就是void *
DWORD WINAPI Func2Proc(LPVOID lpParam);
int tickets = 100;
HANDLE g_hEvent;
int main()
{
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建一个初始状态为无信号人工重置的事件对象,人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程
    HANDLE thread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    HANDLE thread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(thread1);
    CloseHandle(thread2);
    Sleep(4000);
    CloseHandle(g_hEvent);
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求事件对象。刚开始是无信号状态,一直等待
        WaitForSingleObject(g_hEvent, INFINITE);
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread1 sell ticket:" << tickets-- << endl;
        }
        else
        {
            break;
        }
    }
    return 0;
}
DWORD WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        //刚开始无信号状态,一直等待
        WaitForSingleObject(g_hEvent, INFINITE);
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread2 sell ticket:" << tickets-- << endl;
        }
        else
        {
            break;
        }

    }
    return 0;
}

几种多线程同步方式总结

因为事件对象始终处于无信号状态,WaitForSingleObject函数将导致线程暂停,两个线程均未得到该事件对象,阻塞在了WaitForSingleObject语句处。(未往下执行)。

要使线程1或2得到g_hEvent事件对象所有权,则必须将该事件设置为有信号状态。有两种方式实现:

1.创建初始状态为有信号的人工重置事件对象。(不行)

    g_hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);//创建一个初始状态有信号人工重置的事件对象

几种多线程同步方式总结

 2.通过SetEvent设置初始状态为有信号状态时。(不行)

    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建一个初始状态为无信号人工重置的事件对象
    SetEvent(g_hEvent);

几种多线程同步方式总结

 还是不行,因为线程1和线程2可以同时执行(当线程1等待到一个人工重置事件对象后,这个事件对象仍是处于有信号的状态)。所以对于需要保护的代码段,这两线程可以同步执行该代码,从而导致了ticket同步问题。

我们试试改进,当执行完保护的代码后,将事件信号设置为无信号。

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);//LPVOID就是void *
DWORD WINAPI Func2Proc(LPVOID lpParam);
int tickets = 50;
HANDLE g_hEvent;
int main()
{
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建一个初始状态为无信号人工重置的事件对象
    SetEvent(g_hEvent);//设置为有信号
    HANDLE thread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    HANDLE thread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(thread1);
    CloseHandle(thread2);
    Sleep(4000);
    CloseHandle(g_hEvent);
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        //请求事件对象。刚开始是无信号状态,一直等待
        WaitForSingleObject(g_hEvent, INFINITE);
        ResetEvent(g_hEvent);//WaitForSingleObject执行到下一句时,即表示线程1获取到了事件对象。此时调用ResetEvent将对象设置为无信号,防止线程2得到事件对象执行保护的代码段,但是还是存在同步问题:
        //1.单CPU平台下,当线程1获取到事件对象后,其时间片终止了(还来不及执行ResetEvent设置为无信号),线程2也获取了事件对象。(可以理解为g_hEvent对象的获取被2线程并发执行了(单个CPU切换CPU并发执行))
        //2.多CPU平台下,线程1,2可以同时获取事件对象(真是情况应该是1,2中任意一个线程获取事件对象,而没来级的调用ResetEvent将对象设置为无信号,另一个线程也获得了事件对象,只是因为多CPU并行执行)
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread1 sell ticket:" << tickets-- << endl;
            SetEvent(g_hEvent);
        }
        else
        {
            SetEvent(g_hEvent);
            break;
        }
    }
    return 0;
}
DWORD WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        //刚开始无信号状态,一直等待
        WaitForSingleObject(g_hEvent, INFINITE);
        ResetEvent(g_hEvent);
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread2 sell ticket:" << tickets-- << endl;
            SetEvent(g_hEvent);
        }
        else
        {
            SetEvent(g_hEvent);
            break;
        }

    }
    return 0;
}
View Code

几种多线程同步方式总结

 所以,为了实现线程同步,不应该使用人工重置事件对象,而是采用自动重置的事件对象。

1。初始状态为FALSE的自动重置对象

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);
int tickets = 100;
HANDLE g_hEvent;
int main()
{
    HANDLE thread1;
    HANDLE thread2;
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, L"event");///创建一个初始状态为无信号的自动重置的事件对象//
    SetEvent(g_hEvent);
    thread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    thread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);

    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);

}
DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
    while (true)
    {
        WaitForSingleObject(g_hEvent, INFINITE);//无限等待直到获取事件对象,因为它是一个自动事件,一旦获取到所有权。os会将该
       //    事件对象设置为无信号状态
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread1 sell ticket:" << tickets-- << endl;
        }
        else
        {
            break;
        }
    }
    return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
    while (true)
    {
        WaitForSingleObject(g_hEvent, INFINITE);//无限等待直到获取事件对象
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread2 sell ticket:" << tickets-- << endl;
        }
        else
        {
            break;

        }

    }
    return 0;
}
View Code

几种多线程同步方式总结

 通过SetEvent设置为有信号后,当线程1获取到事件对象后(os会自动将事件对象设置为无信号状态),休眠放弃时间片,让线程2去执行,然而此时事件对象为无信号,线程2执行完后

线程1执行关键代码段后,打印tickects信息,while循环执行WaitForSingleObject获取事件对象,但此时已经为无信号状态了。这样就只打印了一次。

 2.初始状态为TRUE的自动重置对象+获取事件对象后执行SetEvent设置为有信号状态

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);
int tickets = 100;
HANDLE g_hEvent;
int main()
{
    HANDLE thread1;
    HANDLE thread2;
    g_hEvent = CreateEvent(NULL, FALSE, TRUE, L"event");///创建一个初始状态为有信号的自动重置的事件对象//
    thread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    thread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);

    Sleep(4000);
    CloseHandle(thread1);
    CloseHandle(thread2);

}
DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
    while (true)
    {
        WaitForSingleObject(g_hEvent, INFINITE);//无限等待直到获取事件对象,因为它是一个自动事件,一旦获取到所有权。os会将该
     //    事件对象设置为无信号状态
        if (tickets > 0)
        {
            cout << "thread1 sell ticket:" << tickets-- << endl;
            SetEvent(g_hEvent);//设置事件对象为有信号状态
        }
        else
        {
            SetEvent(g_hEvent);
            break;
        }
    }
    return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
    while (true)
    {
        WaitForSingleObject(g_hEvent, INFINITE);//无限等待直到获取事件对象
        if (tickets > 0)
        {
            cout << "thread2 sell ticket:" << tickets-- << endl;
            SetEvent(g_hEvent);
        }
        else
        {
            SetEvent(g_hEvent);
            break;

        }

    }
    return 0;
}
View Code

几种多线程同步方式总结

 初始状态为有信号的自动重置事件,当某一线程获得事件对象,执行完受保护代码后,通过执行SetEvent实现线程同步。

关键代码段(临界区)

 关键代码段,也称临界区,工作在用户方式下。它指的是一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。

临界区对象初始化

void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection  //指向临界区对象的指针。

);

等待指定临界区对象的所有权

当调用线程被授予所有权时,该函数返回

void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection //指向临界区对象的指针
);

释放指定临界区对象的所有权

void LeaveCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

释放无主临界区对象使用的所有资源

该函数释放一个没有被任何线程所拥有的临界区对象的所有资源。

void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

临界区对象实现单进程下线程同步

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
#define _ATL_NO_COM_SUPPORT
using namespace std;
DWORD WINAPI Func1Proc(LPVOID lpParam);
DWORD WINAPI Func2Proc(LPVOID lpParam);
int tickets = 100;
CRITICAL_SECTION g_cs;//定义临界区对象

int main()
{
    HANDLE hThread1;
    HANDLE hThread2;
    InitializeCriticalSection(&g_cs);//g_cs为out型,作为返回值
    hThread1 = CreateThread(NULL, 0, Func1Proc, NULL, 0, NULL);
    hThread2 = CreateThread(NULL, 0, Func2Proc, NULL, 0, NULL);
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    Sleep(5000);
    DeleteCriticalSection(&g_cs);//释放没有被任何线程使用的临界区对象的所有资源
    system("pause");
}
DWORD WINAPI Func1Proc(LPVOID lpParam)
{
    while (true)
    {
        EnterCriticalSection(&g_cs);//调用EnterCriticalSection获得指定的临界区对象的所有权
        //该等待等待指定的临界区对象的所有权
        Sleep(1);
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread1 sell ticket:" << tickets-- << endl;
            LeaveCriticalSection(&g_cs);//释放指定的临界区对象的所有权
        }
        else
        {
            LeaveCriticalSection(&g_cs);
            break;
        }
    }
    return 0;
}
DWORD  WINAPI Func2Proc(LPVOID lpParam)
{
    while (true)
    {
        EnterCriticalSection(&g_cs);
        Sleep(1);
        if (tickets > 0)
        {
            Sleep(1);
            cout << "thread2 sell ticket :" << tickets-- << endl;
            LeaveCriticalSection(&g_cs);
        }
        else
        {
            LeaveCriticalSection(&g_cs);
            break;
        }
    }
    return 0;
}
View Code

几种多线程同步方式总结

信号量

信号对象是同步对象维持在零和最大规定值之间的计数。每次线程完成对信号量对象的等待时计数递减,每次线程释放信号量时计数递增。

当计数达到零时,没有更多线程可以成功等待信号量对象状态变为有信号。信号量的状态在其计数大于零时设置为有信号,当其计数为零时设置为无信号。

信号量对象可用于控制可支持有限数量用户的共享资源。它充当一个门,将共享资源的线程数量限制为指定的最大数量

创建信号对象

HANDLE CreateSemaphoreA(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
  LONG                  lInitialCount,
  LONG                  lMaximumCount,
  LPCSTR                lpName
);

lpSemaphoreAttributes

指向SECURITY_ATTRIBUTES 结构的指针如果此参数为NULL,则子进程不能继承句柄。

结构lpSecurityDescriptor成员为新的信号量指定了一个安全描述符。如果此参数为NULL,则信号量获取默认安全描述符。信号量的默认安全描述符中的 ACL 来自创建者的主要令牌或模拟令牌。

lInitialCount

信号量对象的初始计数。该值必须大于或等于零且小于或等于lMaximumCount信号量的状态在其计数大于零时发出信号,为零时未发出信号。每当等待函数释放等待信号量的线程时,计数就会减一。通过调用ReleaseSemaphore函数将计数增加指定的数量 

lMaximumCount

信号量对象的最大计数。该值必须大于零。

lpName

信号量对象的名称。该名称限制为MAX_PATH 个字符。名称比较区分大小写

信号量对象增加计数

BOOL ReleaseSemaphore(
  HANDLE hSemaphore,
  LONG   lReleaseCount,
  LPLONG lpPreviousCount
);

hSemaphore

信号量对象的句柄。该 CreateSemaphore或 OpenSemaphore函数返回该句柄。

此句柄必须具有SEMAPHORE_MODIFY_STATE访问权限。有关详细信息,请参阅 同步对象安全性和访问权限

lReleaseCount

信号量对象的当前计数要增加的量。该值必须大于零。如果指定的数量会导致信号量的计数超过创建信号量时指定的最大计数,则不会更改计数并且函数返回FALSE

lpPreviousCount

一个指向变量的指针,用于接收信号量的先前计数。如果不需要先前的计数,则此参数可以为NULL

信号量实现线程同步

几种多线程同步方式总结
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParam);
DWORD WINAPI Fun2Proc(LPVOID lpParam);
int tickets = 100;
HANDLE hsem;
int main()
{
    HANDLE thread1;
    HANDLE thread2;
    hsem = CreateSemaphore(NULL, 1, 50, L"sema");//创建信号量对象,且开始为signal状态因为第二个参数为1,大于0),最多可以有50个信号
    //hsem = CreateSemaphore(NULL, 0, 1, L"sema");//开始为unsignal状态
    thread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
    thread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);

    Sleep(8000);
    CloseHandle(thread1);
    CloseHandle(thread2);

}
DWORD WINAPI Fun1Proc(LPVOID lpParam)
{
    long count;
    while (true)
    {
        WaitForSingleObject(hsem, INFINITE);//信号量值-1
       //每一个wait过后信号量的数量自动减1,这样就达到了控制同步
        if (tickets > 0)
        {
            cout << "thread1 sell ticket:" << tickets-- << endl;
            Sleep(50);
            ReleaseSemaphore(hsem, 1, &count);////信号量值+1
        }
        else
        {
            ReleaseSemaphore(hsem, 1, &count);////信号量值+1
            break;
        }

    }
    return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParam)
{
    long count;
    while (true)
    {
        WaitForSingleObject(hsem, INFINITE);
        if (tickets > 0)
        {
            cout << "thread2 sell ticket:" << tickets-- << endl;
            Sleep(50);
            ReleaseSemaphore(hsem, 1, &count);
        }
        else
        {
            ReleaseSemaphore(hsem, 1, &count);
            break;
        }

    }
    return 0;
}
View Code

几种多线程同步方式总结

线程按序执行

信号量不仅支持线程同步,还支持线程的按序执行。

几种多线程同步方式总结
#include <Windows.h>
#include <iostream>
using namespace std;
HANDLE Hthread_1;
HANDLE Hthread_2;
HANDLE Hthread_3;

HANDLE Hsem1;
HANDLE Hsem2;
HANDLE Hsem3;

DWORD WINAPI Thread_1(LPVOID param)
{
    for (int i = 0; i < 10; i++)
    {
        DWORD dwWait = WaitForSingleObject(Hsem1, INFINITE);//Hsem1信号量-1
        cout << "A";
        ReleaseSemaphore(Hsem2, 1, NULL);//Hsem2信号量+1
    }
    return 0;
}
DWORD WINAPI Thread_2(LPVOID param)
{
    for (int i = 0; i < 10; i++)
    {
        DWORD dwWait = WaitForSingleObject(Hsem2, INFINITE);//Hsem2信号量-1
        cout << "B";
        ReleaseSemaphore(Hsem3, 1, NULL);//Hsem3信号量+1
    }
    return 0;
}
DWORD WINAPI Thread_3(LPVOID param)
{
    for (int i = 0; i < 10; i++)
    {
        DWORD dwWait = WaitForSingleObject(Hsem3, INFINITE);//Hsem3信号量-1
        cout << "C \n";
        ReleaseSemaphore(Hsem1, 1, NULL);//Hsem1信号量+1
    }
    return 0;
}

int main()
{

    Hsem1 = CreateSemaphore(NULL, 1, 1, NULL);//初始状态有信号的信号对象,信号量为1,最大信号量为1
    Hsem2 = CreateSemaphore(NULL, 0, 1, NULL);
    Hsem3 = CreateSemaphore(NULL, 0, 1, NULL);

    Hthread_1 = CreateThread(NULL, 0, Thread_1, NULL, 0, NULL);
    Hthread_2 = CreateThread(NULL, 0, Thread_2, NULL, 0, NULL);
    Hthread_3 = CreateThread(NULL, 0, Thread_3, NULL, 0, NULL);

    WaitForSingleObject(Hthread_1, INFINITE);
    WaitForSingleObject(Hthread_2, INFINITE);
    WaitForSingleObject(Hthread_3, INFINITE);
    cout << endl << "-------------finish-------------" << endl;
    CloseHandle(Hthread_1);
    CloseHandle(Hthread_2);
    CloseHandle(Hthread_3);
    CloseHandle(Hsem1);
    CloseHandle(Hsem2);
    CloseHandle(Hsem3);
    system("pause");
    return 0;
}
View Code

几种多线程同步方式总结

 各种同步对象的比较

  对象类型 同步形式 特点
临界区 用户方式 单个进程内实现线程同步,不支持跨进程线程同步。单进程下的多线程同步首选 同步速度较快,容易进入死锁状态,在等待进入关键代码段时无法设定超时值
互斥对象 内核对象 跨进程,多进程下的多线程同步 同步速度较慢
事件对象 内核对象 跨进程,多进程下的多线程同步 同步速度较慢
信号量 内核对象 跨进程,多进程下的多线程同步 同步速度较慢,不仅支持线程同步,还支持多个线程按序执行;多个线程实际执行的数目控制

几种多线程同步方式总结

上一篇:pytest(13)-多线程、多进程执行用例


下一篇:简历理论回顾之java基础