【问题标题】:Windows semaphoresWindows 信号量
【发布时间】:2017-11-14 16:05:43
【问题描述】:

我需要实现一个程序,它使用信号量将打开的记事本窗口的数量限制为 10。我在 WM_INITDIALOG 中创建了一个信号量

    semafor = CreateSemaphore(0, 1, 10, "semaphore");

每次我点击按钮打开一个新窗口时,我都会打开那个信号量。但是这并不能阻止我打开超过 10 个窗口。

这是我单击对话框中的按钮打开新窗口时的代码:

case WM_COMMAND:
    switch (LOWORD(wParam)) {
        case ID_OK: 

            semafor = OpenSemaphore(SEMAPHORE_ALL_ACCESS, TRUE, "semaphore");
            if (semafor == NULL) {
                printf("Eroare deschidere semafor empty: %d \n", GetLastError());
                ExitProcess(1);
            }

            BOOL b = CreateProcess("C:\\Windows\\System32\\notepad.exe",
                NULL, NULL, NULL, TRUE, 0, NULL, NULL,
                &si, &pi);

            process[++i] = GetCurrentProcess();

            if (b) {
                dwWaitForChild = WaitForInputIdle(pi.hProcess, 2000);
                switch (dwWaitForChild) {
                case 0:
                    printf("Procesul fiu este ready!\n");
                    break;
                case WAIT_TIMEOUT:
                    printf("Au trecut 2 sec. si procesul fiu nu este ready!\n");
                    break;
                case 0xFFFFFFFF:
                    printf("Eroare!\n");
                    break;
                }

                WaitForMultipleObjects(i, process, TRUE, INFINITE);

                iRasp = MessageBox(NULL, "Terminam procesul fiu?", "Atentie!", MB_YESNO);
                if (iRasp == IDYES) {
                    if (TerminateProcess(pi.hProcess, 2)) {

                        DWORD dwP;

                        GetExitCodeProcess(pi.hProcess, &dwP);
                        printf("Codul de terminare al procesului fiu: %d\n", dwP);
                        ReleaseSemaphore(semafor, 1, NULL);
                        CloseHandle(pi.hProcess);
                        printf("\nProcesul fiu a fost terminat cu succes\n");
                    }
                    else {
                        //tiparim mesajul de eroare 
                        TCHAR buffer[80];
                        LPVOID lpMsgBuf;
                        DWORD dw = GetLastError();

                        FormatMessage(
                            FORMAT_MESSAGE_ALLOCATE_BUFFER |
                            FORMAT_MESSAGE_FROM_SYSTEM,
                            NULL,
                            dw,
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            (LPTSTR)&lpMsgBuf,
                            0, NULL);

                        wsprintf(buffer, "TerminateProcess() a esuat cu eroarea %d: %s",
                            dw, lpMsgBuf);

                        MessageBox(NULL, buffer, "Eroare!", MB_OK);

                        LocalFree(lpMsgBuf);
                    }
                } // rasp YES

            }
            else
                printf("Eroare la crearea procesului fiu!\n");

            return TRUE;
        }
        break;
    }
    return FALSE;
}

【问题讨论】:

  • 这闻起来像XY Problem。您需要向我们提供有关您想要做什么的更广泛的了解。请edit您的问题并详细说明您想要做什么。
  • 为什么需要为此使用信号量而不是简单的计数器变量?

标签: c++ windows winapi process semaphore


【解决方案1】:

您永远不会在信号量上等待(或信号量逻辑中的 P)。

查看winapi example,了解如何使用信号量。在他们的示例中,他们使用WaitForSingleObject。您只使用OpenSemaphore,它只是为您提供信号量句柄,因此您可以在多个进程中使用它,然后如果您想减少它的值并在它为 0 时等待/超时,则需要实际等待它。

您应该在打开信号量之后尝试打开记事本实例之前执行以下操作:

// Try to enter the semaphore gate.

dwWaitResult = WaitForSingleObject( 
    ghSemaphore,   // handle to semaphore
    0L);           // zero-second time-out interval

【讨论】:

  • 此外,正在创建的信号量 initial 计数为 1,因此它只允许在阻塞前等待 1 次。这使得它并不比互斥锁好。而是将初始计数设置为 10,因此在阻塞之前允许等待 10 次。
  • 等待信号量会降低它的价值。释放信号量会增加它的值,除非它达到最大值,在这种情况下它会返回错误。见ReleaseSemaphore
  • 我做了修改:在OpenSemaphore之后添加了WaitForSingleObject,并将信号量的初始计数设置为10,但它没有做任何改变。它不会在打开 10 个窗口时停止。
  • 是的,我的信号量方向混淆了,我的错。
  • 您是否正在检查 WaitForSingleObject 调用的返回值?如果您使用 0 超时,调用会立即返回,但 dwWaitResult 的值将是 WAIT_TIMEOUT(如果我没有记错的话)。然后,您可以使用它来跳过其余的逻辑,而不是打开新的进程。如果您想永远等待,请将超时间隔更改为 INFINITE。
【解决方案2】:

您误用了信号量。一次创建信号量并重复使用它,不要一遍又一遍地重新打开它。但是,更重要的是,您必须等待信号量(例如使用WaitForSingleObject())来减少其计数器,然后使用ReleaseSeamphore() 释放它以增加其计数器。信号量的状态在其计数器大于 0 时发出信号,在 0 时无信号。

因此,您需要创建初始计数为 10 的信号量,然后在每次要创建新进程时等待它。如果等待失败,则说明已经有太多进程在运行。否则,如果等待成功,则计数器已递减,因此创建新进程,然后在进程结束时递增计数器。

但是,话虽如此,您的信号量在您显示的代码的上下文中完全没有用。您想使用信号量来控制正在运行的记事本实例的数量,但是一旦您启动 1 个实例,您就会尝试(不正确,我可能会添加)阻止您的代码,直到该实例退出.因此,您将永远无法一次运行超过 1 个实例,从而使信号量无用。您能够运行超过 1 个的事实可能是由于代码中的错误(您正在等待错误的进程句柄!)。根本不要使用阻塞等待。让系统在每个衍生进程结束时通知您。

在代码中使用信号量的正确方法看起来更像这样:

const UINT WM_PROCESS_ENDED = WM_APP + 1;
const int MAX_PROCESSES = 10;

typedef struct procInfo
{
    HANDLE hProcess;
    HANDLE hWait;
} procInfo;

HANDLE semafor = NULL;
procInfo process[MAX_PROCESSES] = {};
int numProcesses = 0;
HWND hwndDialog;

...

VOID CALLBACK ProcessEnded(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
    PostMessage(hwndDialog, WM_PROCESS_ENDED, 0, (LPARAM)lpParameter);
}

...

case WM_INITDIALOG:
{
    hwndDialog = hwnd;

    semafor = CreateSemaphore(0, MAX_PROCESSES, MAX_PROCESSES, NULL);
    if (semafor == NULL)
    {
        printf("Eroare deschidere semafor empty: %d \n", GetLastError());
        ExitProcess(1);
    }

    break;
}

case WM_DESTROY:
{
    for (int i = 0; i < numProcesses; ++i)
    {
        UnregisterWaitEx(process[i].hWait, INVALID_HANDLE_VALUE);
        TerminateProcess(process[i].hProcess, 2);
        CloseHandle(process[i].hProcess);
    }

    CloseHandle(semafor);
    semafor = NULL;

    hwndDialog = NULL;

    break;
}

case WM_COMMAND:
    switch (LOWORD(wParam))
    {
        case ID_OK:
        {
            if (WaitForSingleObject(semafor, 0) != WAIT_OBJECT_0)
            {
                // too many instances running...
                break;
            }

            if (!CreateProcess("C:\\Windows\\System32\\notepad.exe",
                NULL, NULL, NULL, TRUE, 0, NULL, NULL,
                &si, &pi))
            {
                printf("Eroare la crearea procesului fiu!\n");
                ReleaseSemaphore(semafor, 1, NULL);
                break;
            }

            CloseHandle(pi.hThread);

            dwWaitForChild = WaitForInputIdle(pi.hProcess, 2000);
            switch (dwWaitForChild)
            {
                case 0:
                    printf("Procesul fiu este ready!\n");
                    break;

                case WAIT_TIMEOUT:
                    printf("Au trecut 2 sec. si procesul fiu nu este ready!\n");
                    break;

                case WAIT_FAILED:
                    printf("Eroare!\n");
                    break;
            }

            procInfo info;

            info.hProcess = pi.hProcess;
            if (!RegisterWaitForSingleObject(&info.hWait, pi.hProcess, &ProcessEnded, pi.hProcess, INFINITE, WT_EXECUTELONGFUNCTION | WT_EXECUTEONLYONCE))
            {
                TerminateProcess(pi.hProcess, 2);
                ReleaseSemaphore(semafor, 1, NULL);
                break;
            }

            process[numProcesses++] = info; 
        }
    }

    break;
}

case WM_PROCESS_ENDED:
{
    HANDLE hProcess = (HANDLE)lParam;

    for (int i = 0; i < numProcesses; ++i)
    {
        if (process[i].hProcess == hProcess)
        {
            UnregisterWait(process[i].hWait);

            for (int j = i + 1; j < numProcesses; ++j)
                process[j-1] = process[j];

            --numProcesses;

            break;
        }
    }

    CloseHandle(hProcess);
    ReleaseSemaphore(semafor, 1, NULL);
}

在这种情况下,您可以完全摆脱信号量,因为您已经拥有自己的计数器:

const UINT WM_PROCESS_ENDED = WM_APP + 1;
const int MAX_PROCESSES = 10;

typedef struct procInfo
{
    HANDLE hProcess;
    HANDLE hWait;
} procInfo;

procInfo process[MAX_PROCESSES] = {};
int numProcesses = 0;
HWND hwndDialog;

...

VOID CALLBACK ProcessEnded(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
    PostMessage(hwndDialog, WM_PROCESS_ENDED, 0, (LPARAM)lpParameter);
}

...

case WM_INITDIALOG:
{
    hwndDialog = hwnd;
    break;
}

case WM_DESTROY:
{
    for (int i = 0; i < numProcesses; ++i)
    {
        UnregisterWaitEx(wait[i], INVALID_HANDLE_VALUE);
        TerminateProcess(process[i], 2);
        CloseHandle(process[i]);
    }

    hwndDialog = NULL;

    break;
}

case WM_COMMAND:
    switch (LOWORD(wParam))
    {
        case ID_OK:
        {
            if (numProcesses >= MAX_PROCESSES)
            {
                // too many instances running...
                break;
            }

            if (!CreateProcess("C:\\Windows\\System32\\notepad.exe",
                NULL, NULL, NULL, TRUE, 0, NULL, NULL,
                &si, &pi))
            {
                printf("Eroare la crearea procesului fiu!\n");
                break;
            }

            CloseHandle(pi.hThread);

            dwWaitForChild = WaitForInputIdle(pi.hProcess, 2000);
            switch (dwWaitForChild)
            {
                case 0:
                    printf("Procesul fiu este ready!\n");
                    break;

                case WAIT_TIMEOUT:
                    printf("Au trecut 2 sec. si procesul fiu nu este ready!\n");
                    break;

                case WAIT_FAILED:
                    printf("Eroare!\n");
                    break;
            }

            procInfo info;

            info.hProcess = pi.hProcess;
            if (!RegisterWaitForSingleObject(&info.hWait, pi.hProcess, &ProcessEnded, pi.hProcess, INFINITE, WT_EXECUTELONGFUNCTION | WT_EXECUTEONLYONCE))
            {
                TerminateProcess(pi.hProcess, 2);
                break;
            }

            process[numProcesses++] = info; 
        }
    }

    break;
}

case WM_PROCESS_ENDED:
{
    HANDLE hProcess = (HANDLE)lParam;

    for (int i = 0; i < numProcesses; ++i)
    {
        if (process[i].hProcess == hProcess)
        {
            UnregisterWait(process[i].hWait);

            for (int j = i + 1; j < numProcesses; ++j)
                process[j-1] = process[j];

            --numProcesses;

            break;
        }
    }

    CloseHandle(hProcess);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-24
    • 2013-07-04
    • 2016-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多