我有一个程序可以生成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函数,等待所有其他线程到达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);
}
}
(基于this answer的实施)
该程序偶尔会陷入僵局。如果在那时我使用VS2008来破坏执行并在内部挖掘,那么在Wait...
的{{1}}行中只有一个工作线程在等待。 Barrier()
的值始终为2.
为了使事情更加尴尬,线程工作得越快,它们就越有可能死锁。如果我在发布模式下运行,则死锁大约是10次中的8次。如果我在调试模式下运行并在线程函数中放置一些打印件来查看它们挂起的位置,它们几乎不会挂起。
所以似乎我的一些工作线程被提前杀死,其余部分被困在障碍上。但是,除了读写内存(并调用waitCount
)之外,线程几乎没有任何内容,而且我非常肯定没有发生段错误。我也有可能得出错误的结论,因为(如上面链接的问题所述)我是Win32线程的新手。
这里可能会发生什么,我怎样才能用VS调试这种奇怪的行为?
答案 0 :(得分:3)
有点难以猜测你可能会遇到什么。并行编程是(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);
}
编辑:
好的,既然意图已经澄清(需要处理多次迭代),我会修改答案,但只是稍微修改一下。而不是一个事件数组,有两个:一个用于奇数迭代,一个用于偶数迭代:
// 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]);
}
答案 1 :(得分:3)
如何调试奇怪的线程行为?
不完全是你说的,但答案几乎总是:理解代码真的很好,理解所有可能的结果并找出正在发生的事情。调试器在这里变得不那么有用,因为你可以跟随一个线程并错过导致其他线程失败的原因,或者跟随父进程,在这种情况下执行不再是顺序的,你最终会到处都是。< / p>
现在,解决问题。
pSemaphore = CreateSemaphore(NULL, THREAD_COUNT, THREAD_COUNT, NULL);
lInitialCount [in]:信号量对象的初始计数。该值必须大于或等于零且小于或等于lMaximumCount。信号量的状态在其计数大于零时发出信号,在信号为零时发出信号。只要wait函数释放等待信号量的线程,计数就会减1。通过调用ReleaseSemaphore函数将计数增加指定的数量。
here:
在线程尝试执行任务之前,它使用WaitForSingleObject函数来确定信号量的当前计数是否允许它这样做。 wait函数的超时参数设置为零,因此如果信号量处于非信号状态,函数会立即返回。 WaitForSingleObject将信号量的计数减1。
我们在这里说的是,信号量的count参数告诉您允许一次执行给定任务的线程数。当您最初将计数设置为THREAD_COUNT
时,您允许所有线程访问“资源”,在这种情况下,该资源将继续进行。
您链接的答案使用此信号量的创建方法:
CreateSemaphore(0, 0, 1024, 0)
基本上没有允许任何线程使用该资源。在你的实现中,信号量被发信号通知(&gt; 0),所以一切都进行得很快,直到其中一个线程设法将计数减少到零,此时一些其他线程等待信号量再次发出信号,这可能不是与你的柜台同步发生。请记住,当WaitForSingleObject
返回时,它会减少信号量上的计数器。
在您发布的示例中,设置:
::ReleaseSemaphore(sync.Semaphore, sync.ThreadsCount - 1, 0);
可以使用,因为每个WaitForSingleObject调用都会将信号量的值减少1,并且有threadcount - 1
个要执行的操作,这会在threadcount - 1
WaitForSingleObject
全部返回时发生,因此信号量回到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.
因此,我们将信号量放在这里的用途并不完全是他们设计的信号。
答案 2 :(得分:0)
在你的障碍中,阻止这一行的是什么:
* counter = 0;
在另一个线程执行另一个线程时执行?
LONG wait_count = InterlockedIncrement(计数器);