【问题标题】:Keeping track of all threads in a thread pool跟踪线程池中的所有线程
【发布时间】:2021-10-29 13:33:33
【问题描述】:

我正在考虑使用 Windows 线程 API,但它似乎存在的问题是您无法跟踪所有线程何时完成。您可以跟踪工作项的完成时间,假设您跟踪了每一项。根据我的研究,没有直接的方法可以查询线程池以查看提交的工作项是否已全部完成。

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

VOID CALLBACK MyWorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WORK Work) {
    DWORD threadId = GetCurrentThreadId();

    BOOL bRet = FALSE;

    printf("%d thread\n", threadId);

    return;
}

int main() {
    TP_CALLBACK_ENVIRON CallBackEnviron;
    PTP_POOL pool = NULL;
    PTP_CLEANUP_GROUP cleanupgroup = NULL;
    PTP_WORK_CALLBACK workcallback = MyWorkCallback;
    PTP_TIMER timer = NULL;
    PTP_WORK work = NULL;


    InitializeThreadpoolEnvironment(&CallBackEnviron);
    pool = CreateThreadpool(NULL);
    SetThreadpoolThreadMaximum(pool, 1);
    SetThreadpoolThreadMinimum(pool, 3);
    SetThreadpoolCallbackPool(&CallBackEnviron, pool);
    

    for (int i = 0; i < 10; ++i) {
        work = CreateThreadpoolWork(workcallback, NULL, &CallBackEnviron);
        SubmitThreadpoolWork(work);
        WaitForThreadpoolWorkCallbacks(work, FALSE); // This waits for the work item to get completed.
    }


    return 1;
}

这是一个简单的例子。在WaitForThreadpoolWorkCallbacks 上会发生什么我可以等待那个特定的工作项目。如果我做一些事情,这没问题。但是,如果我正在遍历一个目录并且有数千个文件需要对它们进行处理,我不想跟踪每个单独的工作项。是否可以查询线程池队列以查看是否有任何待处理的内容?或者找出是否有任何线程仍在工作?

【问题讨论】:

  • 旁注: 为什么最大值设置为 1,最小值设置为 3?不应该反过来吗(例如,最大值为 3,最小值为 1)?来自:docs.microsoft.com/en-us/windows/win32/procthread/…你想要一个“清理组”吗?否则,全局(例如)pendcnt 怎么样?主/主线程在排队更多工作时[原子地]增加它。每个工作线程在完成一个工作项后都会递减这个值。排队 all 工作后的主线程可以 [原子地] 在 pendcnt &gt; 0 上旋转。
  • 是的,应该反过来。但是你的想法和我一样。我只是想到了这一点并将其放入我的 VS 代码中。下一个问题,是否有任何条件变量允许我 WaitForSingleObject 直到它达到 0?否则,我将不得不进行 while 循环,直到 pendcnt == 0。
  • 警告: 我使用 linux/posix 线程,所以我只知道我在网上找到的内容。从我提到的链接中,为线程池设置一个清理组并调用CloseThreadpoolCleanupGroupMembers 可能会做到这一点。来自 cmets:等待所有回调完成。 CloseThreadpoolCleanupGroupMembers 也会释放属于清理组成员的对象,因此在调用 CloseThreadpoolCleanupGroupMembers 之后不必对单个对象调用 close 函数。
  • 至于在 pendcnt 上旋转,AFAICT,您需要与 pthread_cond_wait 等效的 win32。我发现:docs.microsoft.com/en-us/windows/win32/sync/condition-variables

标签: c windows multithreading


【解决方案1】:

您需要跟踪活动任务(如评论中的pendcnt)+1。但这不能是全局变量,而是某个结构中的成员。并将指向该结构的指针传递给工作项。在调用 SubmitThreadpoolWork 之前增加此计数器,并在退出之前从回调中减少。但您还需要和事件 - 当计数器变为 0 时将此事件设置为信号状态。并等待来自主线程的事件。如果您在 dll 中的代码可以卸载 - 您还需要在回调中的 SubmitThreadpoolWorkFreeLibraryWhenCallbackReturns 之前引用 dll。同样重要的是计数器值 - 最初为 1(不是 0) - 所以这是 count_of_active_cb + 1,并在开始等待之前将其递减(如果不这样做 - 计数器可以提前变为 0 - 例如在您激活第二个之前第一个回调退出)

class Task
{
    HANDLE _hEvent = 0;
    ULONG _dwThreadId = 0;
    LONG _dwRefCount = 1;
public:

    ~Task()
    {
        if (_hEvent) CloseHandle(_hEvent);
    }

    ULONG Init()
    {
        if (HANDLE hEvent = CreateEvent(0, 0, 0, 0))
        {
            _hEvent = hEvent;
            return NOERROR;
        }

        return GetLastError();
    }

    void AddTask()
    {
        InterlockedIncrementNoFence(&_dwRefCount);
    }

    void EndTask()
    {
        if (!InterlockedDecrement(&_dwRefCount))
        {
            if (_dwThreadId != GetCurrentThreadId())
            {
                if (!SetEvent(_hEvent)) __debugbreak();
            }
        }
    }

    void Wait()
    {
        _dwThreadId = GetCurrentThreadId();
        EndTask();
        if (_dwRefCount && WaitForSingleObject(_hEvent, INFINITE) != WAIT_OBJECT_0) __debugbreak();
    }
};

VOID CALLBACK MyWorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Parameter, PTP_WORK /*Work*/) 
{
    // need only if your code in dll which can be unloaded
    FreeLibraryWhenCallbackReturns(Instance, (HMODULE)&__ImageBase);

    WCHAR sz[32];
    swprintf_s(sz, _countof(sz), L"[%x] thread", GetCurrentThreadId());

    MessageBoxW(0, 0, sz, MB_ICONINFORMATION);

    reinterpret_cast<Task*>(Parameter)->EndTask();
}

void CbDemo()
{
    Task task;
    if (task.Init() == NOERROR)
    {
        ULONG n = 2;
        do 
        {
            if (PTP_WORK pwk = CreateThreadpoolWork(MyWorkCallback, &task, 0))
            {
                HMODULE hmod;
                // need only if your code in dll which can be unloaded
                if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PWSTR)&__ImageBase, &hmod))
                {
                    task.AddTask();
                    SubmitThreadpoolWork(pwk);
                }
                CloseThreadpoolWork(pwk);
            }
        } while (--n);


        MessageBoxW(0, 0, L"Main Thread", MB_ICONWARNING);

        task.Wait();

        __nop();

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多