Unified Parallel C 语言的同步机制
- UID
- 1066743
|
Unified Parallel C 语言的同步机制
概述Unified Parallel C (UPC) 是支持多线程的并行编程语言。它在 C 语言的基础上加入了 upc_barrier,upc_notify, 和 upc_wait 语句来控制多线程之间的同步。另外,它还加入了 upc_fence 语句来控制单个线程对共享数据访问的同步。
upc_barrier 语句作用
upc_barrier 语句 , 又叫做阻挡障碍。在多线程的环境里,各个线程的执行速度是不一样的。使用 upc_barrier 语句可以保证在所有线程执行到某一个程序点之前,任何线程不能向下继续执行。例如,如图 1 所示,程序由 3 个线程同时执行,程序点 A 处有一个 upc_barrier 语句,那么当线程 0 最先执行到这个程序点 A 的时候,线程 0 会停下来等待,直到线程 1 和线程 2 也执行到程序点 A,线程 0 才能继续向下执行。程序点 A 处的 upc_barrier 语句保证所有线程执行到程序点 A 之前,任何线程不能向前执行。
图 1. 阻挡障碍— upc_barrier 语句语法
upc_barrier 整型表达式;
用法规则
此处整型表达式是可选的,其类型必须为整型。整型表达式的存在与否不会影响到程序的性能,整型表达式的作用主要是帮助用户来诊断是否存在不相匹配的 upc_barrier 语句。
upc_barrier整型表达式的作用等同于复合语句 { upc_notify整型表达式; upc_wait整型表达式; }。
upc_barrier 的作用等同于复合语句 { upc_notify; upc_wait; }。我们可以通过清单 1 中的例子来了解 upc_barrier 语句的用法。
清单 1. 无整型表达式的 upc_barrier 语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # include <upc.h> // 假定由四个线程来运行该程序
# include <stdio.h>
shared int a;
int main()
{
if(MYTHREAD == 0)// 由线程 0 来对变量 a 进行初始化
{
a=-1;
}
upc_barrier; // 在所有线程执行到此语句之前,任何线程不得向下执行。这保证了每个线程看到 a
// 的最新的初始值 -1. 假设此处没有 upc_barrier 语句的话,下面各个线程所输出 a 的值很
// 可能不一样。
printf("Th=%d,a=%d\n",MYTHREAD,a); // 每个线程输出变量 a 的值
return 0;
}
|
图 2. 清单 1 程序输出upc_barrier 语句需要被所有线程来调用。若是在 upc_barrier 语句上附带了整型表达式的话,须保证其整型表达式对于所有线程来说是一致的,否则会出现运行错误。如清单 2 的例子所示。
清单 2. 附带整型表达式的 upc_barrier 语句例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #include <upc.h> // 假定由四个线程来运行该程序
#include <stdio.h>
shared int a;
int main(void)
{
if(MYTHREAD == 0)
{
a=1;
}
if(MYTHREAD == 0) // 此处会引起运行错误, 因为线程 0 的 upc_barrier 语句的整型表达式为 1,
// 而对于其他线程(线程 1,线程 2,线程 3),所调用的 upc_barrier 语句
// 的整型表达式为 2.
upc_barrier 1; // 整型表达式为 1
else
upc_barrier 2; // 整型表达式 2
}
|
图 3. 清单 1 程序输出upc_notify 与 upc_wait 语句作用
upc_notify 与 upc_wait 语句,又叫做非阻挡障碍。非阻挡障碍由 upc_notify 和 upc_wait 两个语句组成。其用法如图 4 所示,当线程 0 执行到 upc_notify 语句所在的程序点 a 的时候,线程 0 会通知所有其他的线程(线程 1 和线程 2)它已经执行到了程序点 a。 之后,与 upc_barrier 语句不同的是,线程 0 此时会继续向前执行,直到执行到 upc_wait 语句所在的程序点 b 才会停下来等待其他线程。只有所有其他线程 ( 线程 1 和线程 2) 执行到程序点 a 的时候,线程 0 才会继续从程序点 b 处向下执行。
在程序点 a 和程序点 b 之间,线程 0 可以执行一些与无需与其他线程进行同步的本地计算,这样充分地利用了线程 0 等待其他线程的时间,提高了程序的性能。
图 4. 阻挡障碍— upc_barrier 语句语法
upc_notify 整型表达式;
upc_wait 整型表达式;
用法规则
此处整型表达式是可选的,其类型必须为整型。整型表达式的存在与否不好影响到程序的性能,主要是帮助用户来诊断是否存在不相匹配的 upc_notify 与 upc_wait 语句。
upc_notify 与 upc_wait 语句必须成对的使用。upc_notify 执行之后,必然要执行 upc_wait 语句。两种顺序不可颠倒,而且如果它们具有整型表达式的话,那么这两个整型表达式的值必须是一致的。所谓一致存在两种情况:一种是这两个整型表达式的值是一样的,第二种情况是两个语句中只有一个语句有整型表达式,另外一个语句没有整型表达式。
例如:
{upc_notify 1; upc_wait 1;} // 正确
{upc_notify; upc_wait ;} // 正确
{upc_wait;upc_notify;} // 错误,执行顺序颠倒
{upc_notify 2;upc_wait 3;} // 错误,整型表达式的值不匹配
{upc_notify 2; upc_notify 2; } // 错误,upc_notify 语句必须与 upc_wait 成对的使用
{upc_notify 2; upc_wait;} // 正确 , 当只有一个语句有整型表达式的时候也是匹配的
{upc_notify; upc_wait 1;} // 正确 , 当只有一个语句有整型表达式的时候也是匹配的
下面我们通过清单 4 与清单 5 中的例子来看一下 upc_notify 与 notify_wait 语句的具体用法。在清单 5 中由于 upc_notify 与 upc_wait 语句不匹配,导致程序运行的时候出错,详情见图 6.
清单 4. 无整型表达式的 upc_notify 与 upc_wait 语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # include <upc.h> // 假定 5 个线程来运行该程序
# include <stdio.h>
shared int a;
int b=0;
int main()
{
if(MYTHREAD == 0)
{
a=-1;
}
upc_notify; // 执行此 upc_notify 语句的线程将会通知其他线程它已经执行到此处的
// upc_notify 语句
b +=MYTHREAD; // 在 upc_notify 和 upc_wait 之间,线程可以执行一些不需要与其他线程进行同步的计
// 算,这样可以充分地利用线程等待的时间,提高程序的效率。
upc_wait ; // 执行到 upc_wait 语句的线程会停下来,直到所有线程执行到这里才能继续向下执行。
printf("Th=%d,a=%d,b=%d\n",MYTHREAD,a,b); // 所有线程输出 a 和 b 的值。 我们可以看到上面
// upc_notify 和 upc_wait 语句的使用确保所
// 有的线程中的 a 与 b 值是最新的
return 0;
}
|
图 5. 清单 4 例子输出清单 5. 具有整型表达式的 upc_notify 与 upc_wait 语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # include <upc.h> // 假定 5 个线程来运行该程序
# include <stdio.h>
int main()
{
if(MYTHREAD == 0)
upc_notify 1;
else
upc_notify 2; // 该语句与 upc_wait 1 不匹配
upc_wait 1;
upc_notify 1;
if(MYTHREAD == 0)
upc_wait 1;
else
upc_wait 2; // 该语句与 upc_notify 1 不匹配
return 0;
}
|
图 6. 清单 5 例子输出upc_fence 语句语法
upc_fence;
用法规则
upc_fence 语句确保一个线程在该语句之前所有的操作为其他线程所见之后,才能继续向下执行。
upc_fence 语句用来对线程本身对共享数据的访问进行同步 , 而 upc_barier, upc_notify, 和 upc_wait 是来对线程之间进行同步。与后者比,upc_fence 的执行速度要快得多。
下面我们通过清单 6 中的例子来了解 upc_fence 语句的具体用法。
清单 6. upc_fence 语句的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # include <upc.h>
# include <stdio.h>
# define SUCCESS 0
# define FAILURE 1
# define verify(result,expect) \
{ if ((result)!=(expect)) \
{ \
printf("Error: fail at line %d: mythread=%d, result= %d, expect= %d\n",\
__LINE__, MYTHREAD, result,expect); \
upc_global_exit(FAILURE); \
} \
}
shared int A[THREADS];
int main()
{
int i=0;
A[MYTHREAD]=MYTHREAD;
upc_fence; // 此处的 upc_fence 语句确保了执行该语句的线程在继续向下执行之前,将之前所有对共
// 享数据的读写执行完毕
verify(A[MYTHREAD],MYTHREAD); // 验证数组元素 A[MYTHREAD] 的值是否等于 MYTHREAD
if(MYTHREAD == 0) // 如果执行的线程是线程 0 的话,将元素 A[0] 的值更新为 99
{
A[MYTHREAD]=99;
}
upc_fence; // 在执行过此处的 upc_fence 语句之后,线程 0 中的元素 A[0] 的值应该是 99,而不是 0
if(MYTHREAD == 0) // 线程 0 来验证元素 A[0] 的值是否为 99
{
verify(A[MYTHREAD],99);
}
return SUCCESS;
}
|
|
|
|
|
|
|