多线程矩阵乘法

时间:2010-11-07 03:02:57

标签: multithreading thread-safety

我正在编写一个使用线程级并行执行N X N矩阵乘法的代码
得到C = A X B,
首先,我将矩阵B,分割矩阵转换为块。

一个线程从A和B中取一个块并将它们相乘,
然后将结果添加到C中的相应块 使用malloc()将所有矩阵分配到堆内存中。

但问题是对于相同的输入,
答案有时是错误的,有时是正确的。

我不太确定为什么会这样,但我想代码需要在线程安全方面进行改进。

我发布了部分代码。
是行和列中的块数,即N /块大小 所以线程总数是块^ 3。

while (thread_loaded < total_thread)
 {
  if (thread_count < MAX_THREAD)
  {
   p[thread_loaded].idx = thread_idx;
   p[thread_loaded].matinfo = &mi;   
   threads[thread_loaded] = CreateThread(NULL, 0, matmul_tlp, &p[thread_loaded], 0, &threadid[thread_loaded]);
   if (threads[thread_loaded] != NULL)
   {     
    thread_loaded++;
    thread_count++;
    thread_idx += blocks;
    if (thread_idx >= total_thread)
     thread_idx = (thread_idx % total_thread) + 1;
   }          
  }
 }

用于线程函数,

int i, j, k;  
param* p = (param*)arg; 

int blocks = BLOCKS;
int block_size = BLOCK_SIZE;
int Ar = p->idx / (blocks * blocks); 
int Ac = p->idx % blocks;
int Br = (p->idx / blocks) % blocks; 
int Bc = p->idx % blocks;
int Cr = p->idx / (blocks * blocks);
int Cc = (p->idx % (blocks * blocks)) / blocks;

double** A = p->matinfo->A;
double** B = p->matinfo->B;
double** C = p->matinfo->C;


DWORD res = WaitForSingleObject(mutex[Cr * blocks + Cc], INFINITE);
if (res != WAIT_OBJECT_0)
 perror("cannot acquire mutex.");


for (i = 0; i < block_size; i++)
{
 for (j = 0; j < block_size; j++)
 {
  for (k = 0; k < block_size; k++)
  {
   C[Cr * block_size + i][Cc * block_size + j] +=
    A[Ar * block_size + i][Ac * block_size + k] *
    B[Br * block_size + j][Bc * block_size + k];
  }
 }
}


ReleaseMutex(mutex[Cr * blocks + Cc]);


thread_count--;
return NULL;

如果有人能找到一个洞,我们将不胜感激。 :)

2 个答案:

答案 0 :(得分:1)

在线程函数中有两个正在写入的共享值。 C矩阵和thread_count变量。我没有看到C矩阵如何同步有什么问题,但是值得仔细检查以确保正确计算Cc和Cr值,因为这是你的互斥选择的基础。

最可能的错误来源是thread_count变量,该变量未同步。请记住,a--相当于'a = a - 1,包含一个读操作,后跟一个写操作(它不是原子)。这意味着它很可能在读取和写入之间被抢占,因此变量的某些减少可能会丢失。例如

Thread A reads the value 10 from thread_count.
Thread B reads the value 10 from thread_count.
Thread B writes the value 10-1 into thread_count.
Thread A writes the value 10-1 into thread_count.
thread_count is now equal to 9 when it should be 8.

所以你刚刚丢失了一个信号到线程创建功能。这可能会导致算法在丢失减量时以越来越少的线程运行。可悲的是,除非我错过了什么,否则我不认为它解释了为什么你会得到不好的结果。你的程序运行速度会比实际要慢。

解决此问题的最简单方法是使用condition variable专门针对这些类型的情况而设置,并避免您在此处遇到丢失唤醒问题。

顺便说一下,创建线程通常是一项非常昂贵的操作,因此线程池比创建和销毁大量线程更可取。如果线程池比必要的更复杂,那么可以使用条件变量使线程在完成当前任务后等待新任务。

编辑:我写的解决方案有点太快了,最后我混淆了两个不同的问题。

首先,您要在线程创建函数和计算函数中同步对thread_count变量的访问。要做到这一点,您可以使用互斥锁,也可以使用InterlockedDecrement,InterlockedIncrement等原子操作数(我希望这些是正确的Windows等价物)。请注意,虽然在这种情况下我没有看到使用原子操作数的任何问题,但它们并不像看起来那么简单。在使用它们之前,请确保完全理解它们。

第二个问题是,在等待其中一个线程完成时,您正在创建线程创建函数。您可以通过使用条件变量在主计算完成后发出信号来避免这种情况。这样你就不会无缘无故地占用处理器时间。

还有一件事,确保你的thread_count变量被声明为volatile,以向编译器指示它在函数中读取和写入的顺序很重要。否则,编译器可以自由地重新排序操作以提高性能。

答案 1 :(得分:0)

请注意,矩阵乘法适用于以非阻塞方式使用多个线程。要记住的是c [i,j] =对于所有k,求和[i,k] * b [k,j]。此操作是自包含的,并且在您的线程代码中不需要同步。起始线程只需要对包含a,b,c矩阵的类或结构的引用,需要同步的条目容器以及用于同步条目容器的互斥锁。访问此条目容器需要使用互斥锁来访问它,但其他所有内容都不需要任何互斥锁调用。条目容器中的每个条目都将包含接下来要计算的c矩阵的当前行和列。然后,每个线程将查询条目容器,直到容器为空。唯一真正的任务是在创建/启动线程之前生成条目容器。