pthread互斥的必要性

时间:2015-05-10 11:35:13

标签: c multithreading pthreads mutex

我有一个int array[100],我想要5个线程来计算所有数组元素的总和。

每个线程在其专用范围内迭代20个元素,并将总和写入全局sum变量。

这是必需的互斥体吗?由于所有线程都从独立源读取,因此不需要同步。

for(i=offset; i<offset+range; i++){
  // not used pthread_mutex_lock(&mutex);
  sum += array[i];
  // not used pthread_mutex_unlock(&mutex);
}

这是否会导致不可预测的行为或操作系统是否实际处理此问题?

在这种情况下,是否可以省略互斥锁?我注意到没有它,这些算法运行得更快。

3 个答案:

答案 0 :(得分:3)

是的,您需要同步,因为所有线程都在同时修改sum。这是一个例子:

您有4个元素[a1, a2, a3, a4]和2个帖子t1以及t2sum的数组。首先,让我们说t1获取值a1并将其添加到sum。但它不是原子操作,所以他将sum(它的0)的当前值复制到他的本地空间,让它称之为t1_s,添加a1然后写{{1} }}。但同时sum = t1_s做同样的事情,他得t2值{0,因为sum尚未完成操作)到t1,添加{{1}并写入t2_s。因此,我们获得了a3 sum sum a3的{​​{1}}。这称为数据竞赛。

有多种解决方案:

  1. 您可以像在代码中一样使用a1 + a3,但正如您所提到的那样,它可能很慢,因为互斥锁是昂贵的,所有其他线程都在等待它。
  2. 创建数组(具有线程数的大小)以计算所有线程的本地总和,然后在一个线程中对此数组执行最后一次减少。无需同步。
  3. 没有数组计算每个线程的本地mutex,最后使用互斥锁将所有这些总和添加到共享变量sum_local。我想它会更快(但需要检查)。
  4. 然而,正如@gavinb提到的那样,只有大量数据才有意义。

答案 1 :(得分:3)

  

我有一个int数组[100],我想要5个线程来计算所有数组元素的总和。每个线程在其专用范围内迭代20个元素,并将总和写入全局和变量。

首先,值得指出的是,处理这些少量数据的这么多线程的开销可能不是一个优势。创建线程,序列化访问以及等待它们完成是有代价的。使用这么小的数据集,优化良好的顺序算法可能更快。用不同数量的线程来衡量加速是一个有趣的练习。

  

这是必需的互斥体吗?由于所有线程都从独立源读取,因此不需要同步。

是的 - array变量的读取是独立的,但更新 sum变量不是,因此您需要一个互斥锁来序列化对{{1}的访问权限根据你上面的描述。

然而,这是计算总和的一种非常低效的方式,因为每个线程将竞争(并且等待,因此浪费时间)来访问增量sum。如果计算每个子集的中间总和(如@Werkov也提到的那样),那么等待它们完成并添加中间总和以创建最终总和,不会有读写或写入的争用,因此您不需要互斥锁并且每个线程都可以尽快运行。然后,性能的限制因素可能是内存访问模式和缓存行为。

  

这是否会导致不可预测的行为或操作系统是否实际处理此问题?

是的,当然。操作系统不会为您处理此问题,因为它无法预测您何时/何时访问内存的不同部分,以及出于何种原因。只要其中任何一个数据可能正在写入数据,就必须在线程之间保护共享数据。因此,当线程相互更新sum时,你几乎肯定会得到错误的结果。

  

在这种情况下,是否可以省略互斥锁?我注意到没有它,这些算法运行得更快。

不,绝对不是。它可能运行得更快,但几乎肯定不会给你正确的结果!

答案 2 :(得分:0)

在可以以这种方式对数据进行分区的情况下,跨分区不存在依赖性(即读/写)。在您的示例中,sum变量具有依赖关系,并且互斥锁是必需的。但是,您可以为每个线程使用部分求和累加器,然后只需对这些子结果求和,而无需使用互斥锁。

当然,您无需手动完成此操作。有各种各样的实现,例如参见OpenMP&parallel for and reduction。