Win32 потоки умирают без видимой причины - PullRequest
2 голосов
/ 14 февраля 2011

У меня есть программа, которая порождает 3 рабочих потока, которые выполняют некоторое числовое сжатие, и ожидает их завершения следующим образом:

#define THREAD_COUNT 3
volatile LONG waitCount;
HANDLE pSemaphore;

int main(int argc, char **argv)
{
    // ...

    HANDLE threads[THREAD_COUNT];
    pSemaphore = CreateSemaphore(NULL, THREAD_COUNT, THREAD_COUNT, NULL);
    waitCount = 0;

    for (int j=0; j<THREAD_COUNT; ++j)
    {
        threads[j] = CreateThread(NULL, 0, Iteration, p+j, 0, NULL);
    }
    WaitForMultipleObjects(THREAD_COUNT, threads, TRUE, INFINITE);

    // ...
}

Рабочие потоки используют пользовательскую функцию Barrier в определенных точках кодаподождать, пока все другие потоки не достигнут барьера:

void Barrier(volatile LONG* counter, HANDLE semaphore, int thread_count = THREAD_COUNT)
{
    LONG wait_count = InterlockedIncrement(counter);
    if ( wait_count == thread_count )
    {
        *counter = 0;
        ReleaseSemaphore(semaphore, thread_count - 1, NULL);
    }
    else
    {
        WaitForSingleObject(semaphore, INFINITE);
    }
}

(реализация основана на этот ответ )

Программа иногда блокируется.Если в этот момент я использую VS2008, чтобы прервать выполнение и покопаться во внутренних органах, в строке Wait... в Barrier() ожидается только 1 рабочий поток.Значение waitCount всегда равно 2.

Чтобы сделать вещи еще более неловкими, чем быстрее работают потоки, тем больше вероятность, что они зашли в тупик.Если я запускаю в режиме Release, тупик возникает примерно 8 из 10 раз.Если я запускаю в режиме отладки и помещаю несколько отпечатков в функцию потока, чтобы увидеть, где они висят, они почти никогда не зависают.

Таким образом, кажется, что некоторые из моих рабочих потоков рано заканчиваются, оставляя остальные застрявшими наБарьер.Однако потоки буквально ничего не делают, кроме чтения и записи памяти (и вызова Barrier()), и я совершенно уверен, что никаких ошибок не происходит.Также возможно, что я делаю неверные выводы, поскольку (как уже упоминалось в вопросе, связанном выше) я новичок в потоках Win32.

Что здесь может происходить и как я могу отлаживатьтакое странное поведение с VS?

Ответы [ 3 ]

3 голосов
/ 14 февраля 2011

Трудно догадаться, с чем именно вы столкнетесь. Параллельное программирование - это одно из тех мест, в которых (IMO) стоит придерживаться философии «сделай так, чтобы все было так просто, что, очевидно, правильно», и, к сожалению, я не могу сказать, что твой код Barrier, похоже, подходит. Лично я думаю, что у меня было бы что-то вроде этого:

// define and initialize the array of events use for the barrier:
HANDLE barrier_[thread_count];

for (int i=0; i<thread_count; i++)
    barrier_[i] = CreateEvent(NULL, true, false, NULL);

// ...

Barrier(size_t thread_num) { 
    // Signal that this thread has reached the barrier:
    SetEvent(barrier_[thread_num]); 

    // Then wait for all the threads to reach the barrier:
    WaitForMultipleObjects(thread_count, barrier_, true, INFINITE);
}

Edit:

Хорошо, теперь, когда намерение прояснено (нужно обрабатывать несколько итераций), я бы изменил ответ, но лишь немного. Вместо одного массива событий есть два: один для нечетных итераций и один для четных итераций:

// define and initialize the array of events use for the barrier:
HANDLE barrier_[2][thread_count];

for (int i=0; i<thread_count; i++) {
    barrier_[0][i] = CreateEvent(NULL, true, false, NULL);
    barrier_[1][i] = CreateEvent(NULL, true, false, NULL);
}

// ...

Barrier(size_t thread_num, int iteration) { 
    // Signal that this thread has reached the barrier:
    SetEvent(barrier_[iteration & 1][thread_num]); 

    // Then wait for all the threads to reach the barrier:
    WaitForMultipleObjects(thread_count, &barrier[iteration & 1], true, INFINITE);
    ResetEvent(barrier_[iteration & 1][thread_num]);
}
3 голосов
/ 14 февраля 2011

Как мне отладить странное поведение потоков?

Не совсем то, что вы сказали, но ответ почти всегда: понять код действительно хорошо, понять все возможные результаты и понять, какой из них происходит. Отладчик становится здесь менее полезным, потому что вы можете либо следовать за одним потоком и пропустить то, что вызывает сбой других потоков, либо следовать из родительского, в этом случае выполнение больше не является последовательным, и вы в конечном итоге повсюду.

Теперь о проблеме.

pSemaphore = CreateSemaphore(NULL, THREAD_COUNT, THREAD_COUNT, NULL);

Из документации MSDN :

lInitialCount [in]: начальный счет для объекта семафора. Это значение должно быть больше или равно нулю и меньше или равно lMaximumCount. Состояние семафора сигнализируется, когда его счет больше нуля, и не сигнализируется, когда он равен нулю. Количество уменьшается на единицу, когда функция ожидания освобождает поток, который ожидал семафор. Счет увеличивается на указанную величину путем вызова функции ReleaseSemaphore.

И здесь :

Прежде чем поток попытается выполнить задачу, он использует функцию WaitForSingleObject, чтобы определить, позволяет ли текущий счетчик семафора сделать это. Параметр времени ожидания функции ожидания установлен на ноль, поэтому функция немедленно возвращается, если семафор находится в состоянии без сигнала. WaitForSingleObject уменьшает число семафоров на единицу.

Итак, что мы здесь говорим, так это то, что параметр count семафора сообщает вам, сколько потоков может одновременно выполнять заданную задачу. Когда вы изначально устанавливаете свой счет на THREAD_COUNT, вы разрешаете всем своим потокам доступ к «ресурсу», который в этом случае должен продолжаться.

Ответ, на который вы ссылаетесь, использует этот метод создания семафора:

CreateSemaphore(0, 0, 1024, 0)

Что в основном говорит, что ни одному из потоков не разрешено использовать ресурс. В вашей реализации семафор сигнализируется (> 0), поэтому все продолжается весело, пока одному из потоков не удастся уменьшить счетчик до нуля, после чего какой-то другой поток ожидает, пока семафор снова не будет сигнализирован, что, вероятно, не не происходит синхронно с вашими счетчиками. Помните, что когда WaitForSingleObject возвращает, уменьшается счетчик на семафоре.

В приведенном вами примере, установка:

::ReleaseSemaphore(sync.Semaphore, sync.ThreadsCount - 1, 0);

Работает, потому что каждый из вызовов WaitForSingleObject уменьшает значение семафора на 1, и есть threadcount - 1 из них, что происходит, когда все threadcount - 1 WaitForSingleObject s возвращаются, поэтому семафор возвращается к 0 и, следовательно, снова не сигнализируется, поэтому на следующем проходе все ждут, потому что никому не разрешен доступ к ресурсу сразу.

Короче говоря, установите начальное значение на ноль и посмотрите, исправит ли это.


Редактировать Небольшое объяснение: так что если подумать об этом иначе, семафор подобен n-атомным воротам. Обычно вы делаете следующее:

// Set the number of tickets:
HANDLE Semaphore = CreateSemaphore(0, 20, 200, 0);

// Later on in a thread somewhere...
// Get a ticket in the queue
WaitForSingleObject(Semaphore, INFINITE); 

// Only 20 threads can access this area 
// at once. When one thread has entered 
// this area the available tickets decrease 
// by one. When there are 20 threads here
// all other threads must wait.

// do stuff

ReleaseSemaphore(Semaphore, 1, 0);
// gives back one ticket.

Таким образом, мы используем семафоры не для тех целей, для которых они были разработаны.

0 голосов
/ 14 февраля 2011

В вашем барьере, что мешает этой линии:

* counter = 0;

будет выполняться, пока этот другой выполняется другим потоком?

LONG wait_count = InterlockedIncrement (счетчик);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...