Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Разное поведение программы на Win7 и WinXP

#cpp #windows #многопоточность #winapi


Есть такой код (минимальный рабочий пример):

#include 
#include 

HDC hDC; 
HDC  hDCMem;
HBITMAP hbitmap;
HWND  hwnd; 
int ScreenMaxX;
int ScreenMaxY;

short pattern[8]={~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF};
HBRUSH brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));

void bar(int nLeft, int nTop, int nRight, int nBottom)
{
    RECT rect;
    rect.left   = nLeft;
    rect.right  = nRight;
    rect.top    = nTop;
    rect.bottom = nBottom;

    ::SetTextColor(hDCMem, 0xFF00FF);
    ::SetBkColor(hDCMem, 0xFF00FF);
    //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
    ::FillRect(hDCMem, &rect, brush);
}

void flush(){
    ::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY);
}

CRITICAL_SECTION graphics_cs;

uint8_t thread_cnt=0;
uint8_t total_threads=2;
HANDLE turnstile1=CreateSemaphoreW(nullptr, 0, 2, nullptr);

void thread_func(int num){
    int x,y;
    if(num==0){
        x=20; y=0;
    } else {
        x=110; y=0;
    }

    while(true) {
        while(true) {
            EnterCriticalSection(&graphics_cs);
            if (thread_cnt == num) {
                thread_cnt++;
                bar(x, y, x+40, y+40);
                y+=1;
                //flush();
                if(thread_cnt==total_threads){
                    thread_cnt = 0;
                    flush();
                    ReleaseSemaphore(turnstile1, total_threads, nullptr);
                }
                LeaveCriticalSection(&graphics_cs);
                break;
            } else {
                LeaveCriticalSection(&graphics_cs);
            }
        }

        WaitForSingleObject(turnstile1, INFINITE);

        Sleep(100);
    }
}

void mainx ()
{
    InitializeCriticalSection(&graphics_cs);
    for(int i=0; i


Ответы

Ответ 1



Повозился немного с кодом: похоже, что у Windows XP для memory DC работает какой-то хитрый механизм кэширования при выводе через FillRect, который изменен в последующих версиях Windows. Например, если void bar(..) изменить подобным образом (т.е. для первого треда использовать ::Rectange вместо ::FillRect) void bar(int num, int nLeft, int nTop, int nRight, int nBottom) { RECT rect; rect.left = nLeft; rect.right = nRight; rect.top = nTop; rect.bottom = nBottom; if (num == 0) { ::Rectangle(hDCMem, nLeft, nTop, nRight, nBottom); } else { ::SetTextColor(hDCMem, 0xFF00FF); ::SetBkColor (hDCMem, 0xFF00FF); ::FillRect (hDCMem, &rect, brush); } } то вывод будет идти параллельно. Мои предположения (не уверен, что потянут на полноценный ответ, но все-же): //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern)); - практически любая функция, отличная от ::FillRect, вызывает "сброс" кэширования и фактическое копирование в memory DC данных; //flush(); - ну, это понятно, ::BitBlt безусловно копирует содержимое memory DC в контекст устройства (т.е. физически "рисует"), заодно сбрасывая (перед рисованием) непонятное кэширование ::FillRect. Кстати, если добавить ::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY); последним вызовом в процедуру void bar(..), то рисование также будет идти одновременно на XP. Без исходников точнее ответить сложно; вроде бы, в сети пролетали сорцы XP, но я сходу не нашел. Еще можно было-бы попробовать эту программку в React OS... [UPDATE] По результатам дискуссии в комментариях (см. ниже), мы с автором вопроса пришли к выводу, что, скорее всего, такое поведение объясняется особенностями (или, скорее всего, ошибкой) реализации функции FillRect в GDI Windows XP. Скорее всего, в качестве "трика", повышающего быстродействие, используется внутреннее кэширование заполняемых областей (up to 18 вызовов), до фактической отрисовки в совместимый контекст устройства в памяти. Сказать что-то точнее, без исходных кодов библиотеки GDI32, не представляется возможным.

Ответ 2



Как написано, в ответе @SeNS, причина различия скоростей в том, что прямоугольники отрисовываются после 18 вызовов FillRect. Однако это не ошибка, а, в принципе, документированное поведение. Данный механизм называется «пакетирование» (batching). Оказывается, если вызывать одну и ту же GDI функцию несколько раз в одном и том же потоке, то эти вызовы накапливаются в пакете (в каждом потоке создаётся свой пакет). И пока либо не вызовется другая GDI функция, либо размер пакета не превысит указанный максимальный размер (который возвращает функция GdiGetBatchLimit), либо не заполнится буфер пакетирования, отрисовки не происходит. В моём случае выполняется третье условие, так как максимальный размер пакета в моей системе равен 20-ти, а прямоугольник появляется после 18-го вызова. Поэтому решением проблемы может быть, например, вызов функции GdiFlush после FillRect. Из названия понятно, что эта функция производит выполнение функций в пакете и его очистку. Но, на мой взгляд, в моей программе намного лучше вообще отключить пакетирование: GdiSetBatchLimit(1) в начале кода потока, так как всегда рисуется только один прямоугольник и сразу же выводится на экран. Почему на некоторых Windows 7 пакетирование не работает, несмотря на то, что размер пакета также равен 20-ти, мне всё ещё не ясно. Возможно, дело в более новом менеджере рабочего стола, который как-то более хитро производит отрисовку. Также, не совсем очевидно, какие функции могут попасть в пакет, например, Ellipse срабатывает сразу.

Ответ 3



Вам просто повезло с текущим состоянием Windows 7. Грубо говоря, в вашем примере треды не обязаны работать синхронно, так как синхронизиации, кроме внутреннего счётчика нет.

Комментариев нет:

Отправить комментарий