开发者

Win32 threads dying for no apparent reason

开发者 https://www.devze.com 2023-02-10 04:32 出处:网络
I have a program that spawns 3 worker threads that do some number crunching, and waits for them to finish like so:

I have a program that spawns 3 worker threads that do some number crunching, and waits for them to finish like so:

#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);

    // ...
}

The worker threads use a custom Barrier function at certain points in the code to wait until all other threads reach the 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开发者_开发百科);
    }
}

(Implementation based on this answer)

The program occasionally deadlocks. If at that point I use VS2008 to break execution and dig around in the internals, there is only 1 worker thread waiting on the Wait... line in Barrier(). The value of waitCount is always 2.

To make things even more awkward, the faster the threads work, the more likely they are to deadlock. If I run in Release mode, the deadlock comes about 8 out of 10 times. If I run in Debug mode and put some prints in the thread function to see where they hang, they almost never hang.

So it seems that some of my worker threads are killed early, leaving the rest stuck on the Barrier. However, the threads do literally nothing except read and write memory (and call Barrier()), and I'm quite positive that no segfaults occur. It is also possible that I'm jumping to the wrong conclusions, since (as mentioned in the question linked above) I'm new to Win32 threads.

What could be going on here, and how can I debug this sort of weird behavior with VS?


How do I debug weird thread behaviour?

Not quite what you said, but the answer is almost always: understand the code really well, understand all the possible outcomes and work out which one is happening. A debugger becomes less useful here, because you can either follow one thread and miss out on what is causing other threads to fail, or follow from the parent, in which case execution is no longer sequential and you end up all over the place.

Now, onto the problem.

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

From the MSDN documentation:

lInitialCount [in]: The initial count for the semaphore object. This value must be greater than or equal to zero and less than or equal to lMaximumCount. The state of a semaphore is signaled when its count is greater than zero and nonsignaled when it is zero. The count is decreased by one whenever a wait function releases a thread that was waiting for the semaphore. The count is increased by a specified amount by calling the ReleaseSemaphore function.

And here:

Before a thread attempts to perform the task, it uses the WaitForSingleObject function to determine whether the semaphore's current count permits it to do so. The wait function's time-out parameter is set to zero, so the function returns immediately if the semaphore is in the nonsignaled state. WaitForSingleObject decrements the semaphore's count by one.

So what we're saying here, is that a semaphore's count parameter tells you how many threads are allowed to perform a given task at once. When you set your count initially to THREAD_COUNT you are allowing all your threads access to the "resource" which in this case is to continue onwards.

The answer you link uses this creation method for the semaphore:

CreateSemaphore(0, 0, 1024, 0)

Which basically says none of the threads are permitted to use the resource. In your implementation, the semaphore is signaled (>0), so everything carries on merrily until one of the threads manages to decrease the count to zero, at which point some other thread waits for the semaphore to become signaled again, which probably isn't happening in sync with your counters. Remember when WaitForSingleObject returns it decreases the counter on the semaphore.

In the example you've posted, setting:

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

Works because each of the WaitForSingleObject calls decrease the semaphore's value by 1 and there are threadcount - 1 of them to do, which happen when the threadcount - 1 WaitForSingleObjects all return, so the semaphore is back to 0 and therefore unsignaled again, so on the next pass everybody waits because nobody is allowed to access the resource at once.

So in short, set your initial value to zero and see if that fixes it.


Edit A little explanation: So to think of it a different way, a semaphore is like an n-atomic gate. What you do is usually this:

// 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.

So the use we're putting semaphores to here isn't quite the one for which they were designed.


It's a bit hard to guess exactly what you might be running into. Parallel programming is one of those places that (IMO) it pays to follow the philosophy of "keep it so simple it's obviously correct", and unfortunately I can't say that your Barrier code seems to qualify. Personally, I think I'd have something like this:

// 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:

Okay, now that the intent has been clarified (need to handle multiple iterations), I'd modify the answer, but only slightly. Instead of one array of Events, have two: one for the odd iterations and one for the even iterations:

// 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]);
}


In your barrier, what prevents this line:

*counter = 0;

to be executed while this other one is executed by another thread?

LONG wait_count = InterlockedIncrement(counter);

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号