【问题标题】:How to make two threads strictly alternate using mutexes?如何使用互斥锁使两个线程严格交替?
【发布时间】:2011-10-30 08:34:55
【问题描述】:

我需要创建两个严格交替的线程。这是我使用的示例代码:

#include <Windows.h>
#include <iostream>
using std::cout;
using std::endl;

HANDLE g_hMutex1;
HANDLE g_hMutex2;

DWORD WINAPI ThreadFunc1(LPVOID lpParam);
DWORD WINAPI ThreadFunc2(LPVOID lpParam);

int main(void)
{
    int nCalcNumber = 10;
    DWORD dwThreadId;
    HANDLE pThreadHandles[2];

    g_hMutex1 = CreateMutex(NULL, FALSE, NULL);
    g_hMutex1 = CreateMutex(NULL, FALSE, NULL);

    pThreadHandles[0] = CreateThread(
        NULL,
        0,
        ThreadFunc1,
        static_cast<void*>(&nCalcNumber),
        0,
        &dwThreadId);

    pThreadHandles[1] = CreateThread(
        NULL,
        0,
        ThreadFunc2,
        static_cast<void*>(&nCalcNumber),
        0,
        &dwThreadId);

    WaitForMultipleObjects(2, pThreadHandles, TRUE, INFINITE);

    CloseHandle(pThreadHandles[0]);
    CloseHandle(pThreadHandles[1]);
    CloseHandle(g_hMutex1);
    CloseHandle(g_hMutex2);

    return 0;
}

DWORD WINAPI ThreadFunc1(LPVOID lpParam)
{
    int* nCalcNumber = static_cast<int*>(lpParam);

    for (int i = 0; i < *nCalcNumber; i++)
    {
        WaitForSingleObject(g_hMutex1, INFINITE);

        cout << "Func 1" << endl;

        ReleaseMutex(g_hMutex1);
    }

    return 0;
}

DWORD WINAPI ThreadFunc2(LPVOID lpParam)
{
    int* nCalcNumber = static_cast<int*>(lpParam);

    for (int i = 0; i < *nCalcNumber; i++)
    {
        WaitForSingleObject(g_hMutex1, INFINITE);

        cout << "Func 2" << endl;

        ReleaseMutex(g_hMutex1);
    }

    return 0;
}

我期望收到的结果:

 Func 1
 Func 2
 Func 1
 Func 2
 Func 1
 Func 2
 ...and so one

应该添加什么以获得所需的结果。我可以使用第二个互斥锁吗?

【问题讨论】:

  • 你目前的结果是什么?以及为什么要这样做,因为这会破坏多线程的整个目的。这是作业吗?
  • 我想说你这里根本不需要线程......
  • 您声明了两个互斥量句柄,1 和 2,然后在 1 处创建了两个互斥量,因此泄漏了一个互斥量。然后两个线程获取/释放相同的互斥锁 1。
  • 我同意@DanielMošmondor。如果你需要严格的交替,为什么不使用 Fibers(如果你真的需要两个不同的堆栈),或者两个函数相互让步?
  • @Merlyn Morgan-Graham,这不是家庭作业。这是我自学的下一个阶段。我在工作中通常使用 C#,现在我想学习 C++。

标签: c++ multithreading winapi


【解决方案1】:

正如其他答案中所述,信号量是比互斥锁更好的选择。但作为纯粹的学术练习(家庭作业?),您也可以使用互斥锁来做到这一点。 (强调:这是一个纯粹的学术练习。真正的程序不应该使用这种技术。)

DWORD WINAPI ThreadFunc1(LPVOID lpParam)
{
    int* nCalcNumber = static_cast<int*>(lpParam);

    WaitForSingleObject(g_hMutex2, INFINITE);
    for (int i = 0; i < *nCalcNumber; i++)
    {
        WaitForSingleObject(g_hMutex1, INFINITE);
        ReleaseMutex(g_hMutex2);

        cout << "Func 1" << endl;

        ReleaseMutex(g_hMutex1);
        WaitForSingleObject(g_hMutex2, INFINITE);
    }

    return 0;
}

DWORD WINAPI ThreadFunc2(LPVOID lpParam)
{
    int* nCalcNumber = static_cast<int*>(lpParam);

    WaitForSingleObject(g_hMutex2, INFINITE);
    for (int i = 0; i < *nCalcNumber; i++)
    {
        WaitForSingleObject(g_hMutex1, INFINITE);
        ReleaseMutex(g_hMutex2);

        cout << "Func 2" << endl;

        ReleaseMutex(g_hMutex1);
        WaitForSingleObject(g_hMutex2, INFINITE);
    }

    return 0;
}

Mutex 1 是“我拥有”互斥锁,Mutex 2 是“我想要下一个”互斥锁。

【讨论】:

    【解决方案2】:

    如果你可以使用semaphore:你可以使用semaphore而不是mutex,它和mutex一样容易使用。

    这段代码运行良好:

    #include <windows.h>
    #include <iostream>
    using std::cout;
    using std::endl;
    
    PHANDLE sem1;
    PHANDLE sem2;
    
    DWORD WINAPI ThreadFunc1(LPVOID lpParam);
    DWORD WINAPI ThreadFunc2(LPVOID lpParam);
    
    int main(void)
    {
        int nCalcNumber = 10;
        DWORD dwThreadId;
        HANDLE pThreadHandles[2];
    
        sem1 = (PHANDLE) CreateSemaphore(NULL, 1, 1, NULL);
        sem2 = (PHANDLE) CreateSemaphore(NULL, 0, 1, NULL);
    
    
        pThreadHandles[0] = CreateThread(
                                         NULL,
                                         0,
                                         ThreadFunc1,
                                         static_cast<void*> (&nCalcNumber),
                                         0,
                                         &dwThreadId);
    
        pThreadHandles[1] = CreateThread(
                                         NULL,
                                         0,
                                         ThreadFunc2,
                                         static_cast<void*> (&nCalcNumber),
                                         0,
                                         &dwThreadId);
    
        WaitForMultipleObjects(2, pThreadHandles, TRUE, INFINITE);
    
        CloseHandle(pThreadHandles[0]);
        CloseHandle(pThreadHandles[1]);
        CloseHandle(sem1);
        CloseHandle(sem2);
    
        return 0;
    }
    
    DWORD WINAPI ThreadFunc1(LPVOID lpParam)
    {
        int* nCalcNumber = static_cast<int*> (lpParam);
    
        for (int i = 0; i < *nCalcNumber; i++)
        {
            WaitForSingleObject(sem1, INFINITE);
    
            cout << "Func 1" << endl;
    
            ReleaseSemaphore(sem2, 1 ,NULL);
        }
    
        return 0;
    }
    
    DWORD WINAPI ThreadFunc2(LPVOID lpParam)
    {
        int* nCalcNumber = static_cast<int*> (lpParam);
    
        for (int i = 0; i < *nCalcNumber; i++)
        {
    
            WaitForSingleObject(sem2, INFINITE);
    
            cout << "Func 2" << endl;
    
            ReleaseSemaphore(sem1, 1 ,NULL);
        }
    
        return 0;
    }
    

    【讨论】:

    • 谢谢,这段代码真的有效。我意识到信号量的优势。但是是否有可能解决这个问题,只使用互斥锁?
    • @Tenere:我认为这是更好的解决方案,也许有互斥锁的解决方案。
    • 这里不需要创建命名信号量。实际上,这是一个错误,因为如果您运行程序的两个副本,它们将共享同一对信号量,而不是每个信号量都有自己的私有对。
    • @RaymondChen:是的,但这不是一个在真实环境中运行的程序,它只是一个示例,展示了使用信号量来解决问题。不过,他可以通过替换 NULL 来发出名称,这个问题将得到解决。我更新了答案。
    【解决方案3】:

    您假设操作系统实际上支持这一点。窗户没有。它不能保证调度,除了没有饥饿。

    所以你需要做的是设置一个标志变量,这样每个线程都会改变它以允许另一个线程运行。例如,如果它是真的 - 运行,如果它是假的 - 释放互斥锁并休眠一段时间,而另一个线程 - 正好相反。睡眠在这里很重要,可以避免饥饿和死锁。我认为它可以是 Sleep(0) (检查它在 Windows 中是否意味着“屈服”,我不确定)。

    当然,检查应该在使用互斥锁时进行,并且在运行结束时,每个线程都会将变量更改为相反的值 - 以允许其他线程运行并阻塞自身,直到其他线程运行并改回来。

    通过将变量设为以线程数为模的计数器,并且每个线程在运行结束时增加值,并检查该值以模数为线程的顺序,可以轻松更改为2个以上的线程开始执行。

    编辑

    volatile bool flag = false;
    
    DWORD WINAPI ThreadFunc1(LPVOID lpParam)
    {
        int* nCalcNumber = static_cast<int*>(lpParam);
    
        for (int i = 0; i < *nCalcNumber; /*no-op*/;)
        {
            WaitForSingleObject(g_hMutex1, INFINITE);
    
            if (flag) {Sleep(0); continue;}
    
            cout << "Func 1" << endl;
    
            flag = true;
            i++;
            ReleaseMutex(g_hMutex1);
        }
    
        return 0;
    }
    
    DWORD WINAPI ThreadFunc2(LPVOID lpParam)
    {
        int* nCalcNumber = static_cast<int*>(lpParam);
    
        for (int i = 0; i < *nCalcNumber; /*no-op*/;)
        {
            WaitForSingleObject(g_hMutex1, INFINITE);
            if (!flag) {Sleep(0); continue;}
    
            cout << "Func 2" << endl;
    
            flag = false;
            i++;
            ReleaseMutex(g_hMutex1);
        }
    
        return 0;
    }
    

    【讨论】:

    • 感谢您的回答。我想你的决定如下:WaitForSingleObject,while 循环,我检查全局变量,cout 到流,更改全局变量,ReleaseMutex。所以我只能使用一个互斥锁。有没有没有while循环的解决方案?
    • @tenere,while 循环与互斥锁无关,这是您管理线程的方式。在您的代码中,您有一个完全相同的 for 循环,因此您不需要任何额外的循环,只需在线程实际运行时增加 for 循环计数器,否则休眠。
    • @Tenere - 你的原始代码几乎就在那里。两个互斥锁本来可以工作,但您在同一个句柄上创建了两个互斥锁。如果创建 1 和 2,线程 A 可以释放互斥锁 2 并在互斥锁 1 上等待。线程 B 可以释放互斥锁 1 并在互斥锁 2 上等待。实际上,您正在两个线程之间交换令牌。轮询产量/睡眠解决方案几乎总是不好的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多