cilk之User Guide学习笔记(5)数据竞争加锁和reducers

说明:

下载User Guide: http://software.intel.com/zh-cn/forums/showthread.php?t=77996&o=a&s=lr(Cilk_User_Guide.pdf)

主要是对该用户指南(中文版)的一些学习笔记和简化并更加自己的理解添加一些代码示例,可以参考原文档获取更多细节。


cilk的主要内容就是三个关键字cilk_spawn,cilk_sync,cilk_for,但是除此之外,需要考虑的一个问题是数据竞争的相关问题,对于任何并行编程多线程编程,数据竞争都是需要考虑的一个问题。那么,针对cilk程序的数据竞争,该如何处理呢?

cilk程序的数据竞争和其他并行编程数据竞争一样,解决方法无非就是:重新构造代码、修改算法、使用局部变量替代全局变量、加锁等。其中,大部分最终需要通过加锁解决数据竞争。cilk本身没有提供加锁的数据结构,但是,cilk能识别其它框架的一些加锁机制。

Cilk可以识别下列加锁机制:

1. Intel® Threading Building Blocks库提供了tbb::mutex用于创建临界区代码。临界区代码中对共享内存及其它共享资源的更新与访问是安全的。Intel® Parallel Studio工具可以识别该加锁机制,对于通过tbb::mutex进行保护的内存访问不会报告数据竞争。

2. Windows*操作系统:CRITICAL_SECTION对象的功能和tbb::mutex对象基本相同。对于通过EnterCriticalSection()、TryEnterCriticalSection()或LeaveCriticalSection()进行保护的访问,Intel® Parallel Studio工具不会报告数据竞争。

3. Linux*操作系统:Posix* Pthread互斥锁(pthread_mutex_t)的功能和tbb::mutex基本相同。对于通过pthread_mutex_lock()、 pthread_mutex_trylock()或pthread_mutex_unlock()进行保护的访问,Intel® Parallel Studio工具不会报告数据竞争。

4. Intel® Parallel Studio工具可以识别原子机器指令, C/C++程序员可以通过编译器基本函数来使用这些指令。

另外,对于死锁等在cilk中仍然和其他并行程序理解类似。对于cilk程序的性能,除了粒度可以影响之外,一般的并行程序的相关问题如锁竞争、高速缓存效率和内存带宽、伪共享、原子操作等等都可能同样影响cilk程序的性能。

除了上面的修改代码和加锁解决数据竞争的方法,还有一种特殊的解决数据竞争的方法:reducer。当然,这种方法本身也只能适用于一些特殊的数据竞争。关于reducer的功能理解,和OpenMP的reduction子句的功能是一样的,参考:http://blog.csdn.net/gengshenghong/article/details/7000685。当然,这是说功能上是一致的,在cikl中reducer是一种数据结构(数据类型),而openmp的reduction是一个子句。

Reducers有下面这些重要属性:
Reducers允许无竞争的可靠存取非本地变量。
Reducers不需要加锁,因而避免了由于对非本地变量加锁而带来的锁竞争问题, 以及由此而引起的无法并行的问题。
在正确的定义和使用情况下, Reducers保留了串行语义。 使用Reducers的Cilk程序的结果与串行版本的结果是一致的, 该结果不依赖于目标机器的处理器数目, 也不依赖于工作线程的调度。 Reducers的使用不需要对现存的代码结构做明显的修改。
Reducers的实现是高效的。
与定义在控制结构比如循环上的实现不一样, Reducers的使用不依赖于程序的控制结构。

参考http://software.intel.com/zh-cn/blogs/2010/06/25/intel-cilk-plus-reducer/和user guide理解reducer视图。

总体来说,个人觉得,reducer是很容易理解的,主要就是用于一些“迭代”操作,如“迭加”,“迭乘”等。

下面是一个求和的例子,用于理解reducer的使用:

// File: test1.cpp
#include <stdio.h>
#include <cilk/cilk.h>
#include <cilk/reducer_opadd.h>

#define N	1000000
cilk::reducer_opadd<unsigned long long> sum;	// 不需要初始化为0,如果需要进行初始化其他值,需要自己修改代码处理。
unsigned long long sum0=0, sum1=0;

int main() {
	// case1
	for(int i=0;i < N;i++) {
		sum0 = sum0 + i;
	}
	printf("Correct Sum is 		%d\n",sum0);
	
	// case2
	cilk_for(int i=0;i < N;i++) {
		sum1 = sum1 + i;
	}
	printf("No Reducer Sum is 	%d\n",sum1);
	
	// case3
	cilk_for(int i=0;i < N;i++) {
		sum = sum + i;
	}
	printf("Reducer Sum is 		%d\n",sum.get_value());
	
	return 0;
}

你可能感兴趣的:(多线程,数据结构,编程,user,工具,parallel)