gpt4 book ai didi

multithreading - 多线程程序在 _multiple_ CPU 上变得无响应,但在单个 CPU 上正常(更新 ListView 时)

转载 作者:行者123 更新时间:2023-12-04 11:41:48 25 4
gpt4 key购买 nike

更新:我已经重现了这个问题!向下滚动以查看代码。
快速笔记

  • 我的 Core i5 CPU 有 2 个内核,超线程。
  • 如果我调用 SetProcessAffinityMask(GetCurrentProcess(), 1) , 一切都很好 ,即使程序仍然是多线程的。
  • 如果我不这样做,并且程序在 Windows XP 上运行(在 Windows 7 x64 上很好!),我的 GUI 在我滚动 ListView 和加载图标时开始锁定几秒钟。

  • 问题
    基本上,当我在 Windows XP(Windows 7 很好)上运行下面发布的程序(我的原始代码的简化版本)时,除非我为所有线程强制使用相同的逻辑 CPU,否则程序 UI 开始滞后半秒或者。
    ( 注意 :这里对这篇文章进行了大量编辑,因为我进一步调查了这个问题。)
    请注意,线程数是相同的——只是关联掩码不同。
    我已经尝试过使用两种不同的消息传递方法:内置 GetMessage以及我自己的 BackgroundWorker .
    结果? BackgroundWorker受益于 1 个逻辑 CPU 的亲和性(几乎没有延迟),而 GetMessage完全被这个伤害了,(滞后现在长了几秒钟)。
    我无法弄清楚为什么会发生这种情况——多个 CPU 不应该比单个 CPU 工作得更好吗?!
    当线程数相同时,为什么会有这样的延迟?

    更多统计: GetLogicalProcessorInformation返回:
    0x0: {ProcessorMask=0x0000000000000003 Relationship=RelationProcessorCore ...}
    0x1: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
    0x2: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
    0x3: {ProcessorMask=0x0000000000000003 Relationship=RelationCache ...}
    0x4: {ProcessorMask=0x000000000000000f Relationship=RelationProcessorPackage ...}
    0x5: {ProcessorMask=0x000000000000000c Relationship=RelationProcessorCore ...}
    0x6: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
    0x7: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
    0x8: {ProcessorMask=0x000000000000000c Relationship=RelationCache ...}
    0x9: {ProcessorMask=0x000000000000000f Relationship=RelationCache ...}
    0xa: {ProcessorMask=0x000000000000000f Relationship=RelationNumaNode ...}

    编码
    下面的代码应该在 Windows XP SP3 上显示此问题。
    (至少,它可以在我的电脑上!)
    比较这两个:
  • 正常运行程序,然后滚动。你应该看到滞后。
  • 使用 affinity 运行程序命令行参数,然后滚动。它应该几乎完全光滑。

  • 为什么会发生这种情况?
    #define _WIN32_WINNT 0x502

    #include <tchar.h>
    #include <Windows.h>
    #include <CommCtrl.h>

    #pragma comment(lib, "kernel32.lib")
    #pragma comment(lib, "comctl32.lib")
    #pragma comment(lib, "user32.lib")

    LONGLONG startTick = 0;

    LONGLONG QPC()
    { LARGE_INTEGER v; QueryPerformanceCounter(&v); return v.QuadPart; }

    LONGLONG QPF()
    { LARGE_INTEGER v; QueryPerformanceFrequency(&v); return v.QuadPart; }

    bool logging = false;
    bool const useWindowMessaging = true; // GetMessage() or BackgroundWorker?
    bool const autoScroll = false; // for testing

    class BackgroundWorker
    {
    struct Thunk
    {
    virtual void operator()() = 0;
    virtual ~Thunk() { }
    };
    class CSLock
    {
    CRITICAL_SECTION& cs;
    public:
    CSLock(CRITICAL_SECTION& criticalSection)
    : cs(criticalSection)
    { EnterCriticalSection(&this->cs); }
    ~CSLock() { LeaveCriticalSection(&this->cs); }
    };
    template<typename T>
    class ScopedPtr
    {
    T *p;
    ScopedPtr(ScopedPtr const &) { }
    ScopedPtr &operator =(ScopedPtr const &) { }
    public:
    ScopedPtr() : p(NULL) { }
    explicit ScopedPtr(T *p) : p(p) { }
    ~ScopedPtr() { delete p; }
    T *operator ->() { return p; }
    T &operator *() { return *p; }
    ScopedPtr &operator =(T *p)
    {
    if (this->p != NULL) { __debugbreak(); }
    this->p = p;
    return *this;
    }
    operator T *const &() { return this->p; }
    };

    Thunk **const todo;
    size_t nToDo;
    CRITICAL_SECTION criticalSection;
    DWORD tid;
    HANDLE hThread, hSemaphore;
    volatile bool stop;
    static size_t const MAX_TASKS = 1 << 18; // big enough for testing

    static DWORD CALLBACK entry(void *arg)
    { return ((BackgroundWorker *)arg)->process(); }

    public:
    BackgroundWorker()
    : nToDo(0), todo(new Thunk *[MAX_TASKS]), stop(false), tid(0),
    hSemaphore(CreateSemaphore(NULL, 0, 1 << 30, NULL)),
    hThread(CreateThread(NULL, 0, entry, this, CREATE_SUSPENDED, &tid))
    {
    InitializeCriticalSection(&this->criticalSection);
    ResumeThread(this->hThread);
    }

    ~BackgroundWorker()
    {
    // Clear all the tasks
    this->stop = true;
    this->clear();
    LONG prev;
    if (!ReleaseSemaphore(this->hSemaphore, 1, &prev) ||
    WaitForSingleObject(this->hThread, INFINITE) != WAIT_OBJECT_0)
    { __debugbreak(); }
    CloseHandle(this->hSemaphore);
    CloseHandle(this->hThread);
    DeleteCriticalSection(&this->criticalSection);
    delete [] this->todo;
    }

    void clear()
    {
    CSLock lock(this->criticalSection);
    while (this->nToDo > 0)
    {
    delete this->todo[--this->nToDo];
    }
    }

    unsigned int process()
    {
    DWORD result;
    while ((result = WaitForSingleObject(this->hSemaphore, INFINITE))
    == WAIT_OBJECT_0)
    {
    if (this->stop) { result = ERROR_CANCELLED; break; }
    ScopedPtr<Thunk> next;
    {
    CSLock lock(this->criticalSection);
    if (this->nToDo > 0)
    {
    next = this->todo[--this->nToDo];
    this->todo[this->nToDo] = NULL; // for debugging
    }
    }
    if (next) { (*next)(); }
    }
    return result;
    }

    template<typename Func>
    void add(Func const &func)
    {
    CSLock lock(this->criticalSection);
    struct FThunk : public virtual Thunk
    {
    Func func;
    FThunk(Func const &func) : func(func) { }
    void operator()() { this->func(); }
    };
    DWORD exitCode;
    if (GetExitCodeThread(this->hThread, &exitCode) &&
    exitCode == STILL_ACTIVE)
    {
    if (this->nToDo >= MAX_TASKS) { __debugbreak(); /*too many*/ }
    if (this->todo[this->nToDo] != NULL) { __debugbreak(); }
    this->todo[this->nToDo++] = new FThunk(func);
    LONG prev;
    if (!ReleaseSemaphore(this->hSemaphore, 1, &prev))
    { __debugbreak(); }
    }
    else { __debugbreak(); }
    }
    };

    LRESULT CALLBACK MyWindowProc(
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    enum { IDC_LISTVIEW = 101 };
    switch (uMsg)
    {
    case WM_CREATE:
    {
    RECT rc; GetClientRect(hWnd, &rc);

    HWND const hWndListView = CreateWindowEx(
    WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
    WS_CHILDWINDOW | WS_VISIBLE | LVS_REPORT |
    LVS_SHOWSELALWAYS | LVS_SINGLESEL | WS_TABSTOP,
    rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
    hWnd, (HMENU)IDC_LISTVIEW, NULL, NULL);

    int const cx = GetSystemMetrics(SM_CXSMICON),
    cy = GetSystemMetrics(SM_CYSMICON);

    HIMAGELIST const hImgList =
    ImageList_Create(
    GetSystemMetrics(SM_CXSMICON),
    GetSystemMetrics(SM_CYSMICON),
    ILC_COLOR32, 1024, 1024);

    ImageList_AddIcon(hImgList, (HICON)LoadImage(
    NULL, IDI_INFORMATION, IMAGE_ICON, cx, cy, LR_SHARED));

    LVCOLUMN col = { LVCF_TEXT | LVCF_WIDTH, 0, 500, TEXT("Name") };
    ListView_InsertColumn(hWndListView, 0, &col);
    ListView_SetExtendedListViewStyle(hWndListView,
    LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
    ListView_SetImageList(hWndListView, hImgList, LVSIL_SMALL);

    for (int i = 0; i < (1 << 11); i++)
    {
    TCHAR text[128]; _stprintf(text, _T("Item %d"), i);
    LVITEM item =
    {
    LVIF_IMAGE | LVIF_TEXT, i, 0, 0, 0,
    text, 0, I_IMAGECALLBACK
    };
    ListView_InsertItem(hWndListView, &item);
    }

    if (autoScroll)
    {
    SetTimer(hWnd, 0, 1, NULL);
    }

    break;
    }
    case WM_TIMER:
    {
    HWND const hWndListView = GetDlgItem(hWnd, IDC_LISTVIEW);
    RECT rc; GetClientRect(hWndListView, &rc);
    if (!ListView_Scroll(hWndListView, 0, rc.bottom - rc.top))
    {
    KillTimer(hWnd, 0);
    }
    break;
    }
    case WM_NULL:
    {
    HWND const hWndListView = GetDlgItem(hWnd, IDC_LISTVIEW);
    int const iItem = (int)lParam;
    if (logging)
    {
    _tprintf(_T("@%I64lld ms:")
    _T(" Received: #%d\n"),
    (QPC() - startTick) * 1000 / QPF(), iItem);
    }
    int const iImage = 0;
    LVITEM const item = {LVIF_IMAGE, iItem, 0, 0, 0, NULL, 0, iImage};
    ListView_SetItem(hWndListView, &item);
    ListView_Update(hWndListView, iItem);
    break;
    }
    case WM_NOTIFY:
    {
    LPNMHDR const pNMHDR = (LPNMHDR)lParam;
    switch (pNMHDR->code)
    {
    case LVN_GETDISPINFO:
    {
    NMLVDISPINFO *const pInfo = (NMLVDISPINFO *)lParam;
    struct Callback
    {
    HWND hWnd;
    int iItem;
    void operator()()
    {
    if (logging)
    {
    _tprintf(_T("@%I64lld ms: Sent: #%d\n"),
    (QPC() - startTick) * 1000 / QPF(),
    iItem);
    }
    PostMessage(hWnd, WM_NULL, 0, iItem);
    }
    };
    if (pInfo->item.iImage == I_IMAGECALLBACK)
    {
    if (useWindowMessaging)
    {
    DWORD const tid =
    (DWORD)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    PostThreadMessage(
    tid, WM_NULL, 0, pInfo->item.iItem);
    }
    else
    {
    Callback callback = { hWnd, pInfo->item.iItem };
    if (logging)
    {
    _tprintf(_T("@%I64lld ms: Queued: #%d\n"),
    (QPC() - startTick) * 1000 / QPF(),
    pInfo->item.iItem);
    }
    ((BackgroundWorker *)
    GetWindowLongPtr(hWnd, GWLP_USERDATA))
    ->add(callback);
    }
    }
    break;
    }
    }
    break;
    }

    case WM_CLOSE:
    {
    PostQuitMessage(0);
    break;
    }
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }

    DWORD WINAPI BackgroundWorkerThread(LPVOID lpParameter)
    {
    HWND const hWnd = (HWND)lpParameter;
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0 && msg.message != WM_QUIT)
    {
    if (msg.message == WM_NULL)
    {
    PostMessage(hWnd, msg.message, msg.wParam, msg.lParam);
    }
    }
    return 0;
    }

    int _tmain(int argc, LPTSTR argv[])
    {
    startTick = QPC();
    bool const affinity = argc >= 2 && _tcsicmp(argv[1], _T("affinity")) == 0;
    if (affinity)
    { SetProcessAffinityMask(GetCurrentProcess(), 1 << 0); }

    bool const log = logging; // disable temporarily
    logging = false;

    WNDCLASS wndClass =
    {
    0, &MyWindowProc, 0, 0, NULL, NULL, LoadCursor(NULL, IDC_ARROW),
    GetSysColorBrush(COLOR_3DFACE), NULL, TEXT("MyClass")
    };
    HWND const hWnd = CreateWindow(
    MAKEINTATOM(RegisterClass(&wndClass)),
    affinity ? TEXT("Window (1 CPU)") : TEXT("Window (All CPUs)"),
    WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);

    BackgroundWorker iconLoader;
    DWORD tid = 0;
    if (useWindowMessaging)
    {
    CreateThread(NULL, 0, &BackgroundWorkerThread, (LPVOID)hWnd, 0, &tid);
    SetWindowLongPtr(hWnd, GWLP_USERDATA, tid);
    }
    else { SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)&iconLoader); }

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
    if (!IsDialogMessage(hWnd, &msg))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    if (msg.message == WM_TIMER ||
    !PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
    { logging = log; }
    }

    PostThreadMessage(tid, WM_QUIT, 0, 0);
    return 0;
    }

    最佳答案

    根据您在 http://ideone.com/fa2fM 上发布的线程间计时,看起来这里存在公平问题。仅基于此假设,以下是我对感知滞后的明显原因和问题的潜在解决方案的推理。

    貌似有大量LVN_GETDISPINFO由窗口进程在一个线程上生成和处理的消息,虽然后台工作线程能够跟上并以相同的速率将消息发布回窗口,但它发布的 WM_NULL 消息在队列中很远,以至于它在他们得到处理之前需要时间。

    当您设置处理器关联掩码时,您会在系统中引入更多的公平性,因为同一个处理器必须为两个线程提供服务,这将限制 LVN_GETDISPINFO 的速率。消息是相对于非关联情况生成的。这意味着当您发布 WM_NULL 消息时,window proc 消息队列可能没有那么深,这反过来意味着它们将被“更快”处理。

    看来您需要以某种方式绕过排队效应。使用 SendMessage , SendMessageCallbackSendNotifyMessage而不是 PostMessage可能是这样做的方法。在 SendMessage在这种情况下,您的工作线程将阻塞,直到窗口 proc 线程完成其当前消息并处理发送的 WM_NULL 消息,但您将能够更均匀地将 WM_NULL 消息注入(inject)消息处理流中。见 this page有关排队与非排队消息处理的说明。

    如果您选择使用 SendMessage ,但由于SendMessage 的阻塞特性,您不想限制获取图标的速度。 ,那么您可以使用第三个线程。您的 I/O 线程会将消息发布到第三个线程,而第三个线程使用 SendMessage将图标更新注入(inject) UI 线程。通过这种方式,您可以控制满意的图标请求队列,而不是将它们交错到窗口 proc 消息队列中。

    至于 Win7 和 WinXP 之间的行为差​​异,可能有很多原因导致您在 Win7 上看不到这种效果。可能是 ListView 公共(public)控件的实现方式不同,并限制了生成 LVN_GETDISPINFO 消息的速率。或者也许 Win7 中的线程调度机制更频繁或更公平地切换线程上下文。

    编辑:

    根据您的最新更改,尝试以下操作:

    ...

    struct Callback
    {
    HWND hWnd;
    int iItem;
    void operator()()
    {
    if (logging)
    {
    _tprintf(_T("@%I64lld ms: Sent: #%d\n"),
    (QPC() - startTick) * 1000 / QPF(),
    iItem);
    }
    SendNotifyMessage(hWnd, WM_NULL, 0, iItem); // <----
    }
    };


    ...

    DWORD WINAPI BackgroundWorkerThread(LPVOID lpParameter)
    {
    HWND const hWnd = (HWND)lpParameter;
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0 && msg.message != WM_QUIT)
    {
    if (msg.message == WM_NULL)
    {
    SendNotifyMessage(hWnd, msg.message, msg.wParam, msg.lParam); // <----
    }
    }
    return 0;
    }

    编辑 2:

    在确定 LVN_GETDISPINFO正在使用 SendMessage 将消息放入队列中而不是 PostMessage , 我们不能使用 SendMessage我们自己绕过他们。

    仍然假设在从工作线程发回图标结果之前,wndproc 正在处理大量消息,我们需要另一种方法来在这些更新准备好后立即处理它们。

    这是想法:
  • 工作线程将结果放入一个同步的类似队列的数据结构中,然后(使用 PostMessage )向 wndproc 发送 WM_NULL 消息(以确保 wndproc 在将来的某个时间执行)。
  • 在 wndproc 的顶部(在 case 语句之前),UI 线程检查同步的类似队列的数据结构以查看是否有任何结果,如果有,则从类似队列的数据结构中移除一个或多个结果并处理他们。
  • 关于multithreading - 多线程程序在 _multiple_ CPU 上变得无响应,但在单个 CPU 上正常(更新 ListView 时),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11336123/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com