OpenMP task construct 實(shí)現(xiàn)原理及源碼示例解析
前言
在本篇文章當(dāng)中主要給大家介紹在 OpenMP 當(dāng)中 task 的實(shí)現(xiàn)原理,以及他調(diào)用的相關(guān)的庫(kù)函數(shù)的具體實(shí)現(xiàn)。
在本篇文章當(dāng)中最重要的就是理解整個(gè) OpenMP 的運(yùn)行機(jī)制。
從編譯器角度看 task construct
在本小節(jié)當(dāng)中主要給大家分析一下編譯器將 openmp 的 task construct 編譯成什么樣子,下面是一個(gè) OpenMP 的 task 程序例子:
#include <stdio.h>
#include <omp.h>
int main()
{
#pragma omp parallel num_threads(4) default(none)
{
#pragma omp task default(none)
{
printf("Hello World from tid = %d\n", omp_get_thread_num());
}
}
return 0;
}
首先先捋一下整個(gè)程序被編譯之后的執(zhí)行流程,經(jīng)過(guò)前面的文章的學(xué)習(xí),我們已經(jīng)知道了并行域當(dāng)中的代碼會(huì)被編譯器編譯成一個(gè)函數(shù),關(guān)于這一點(diǎn)我們已經(jīng)在前面的很多文章當(dāng)中已經(jīng)討論過(guò)了,就不再進(jìn)行復(fù)述。事實(shí)上 task construct 和 parallel construct 一樣,task construct 也會(huì)被編譯成一個(gè)函數(shù),同樣的這個(gè)函數(shù)也會(huì)被作為一個(gè)參數(shù)傳遞給 OpenMP 內(nèi)部,被傳遞的這個(gè)函數(shù)可能被立即執(zhí)行,也可能在函數(shù) GOMP_parallel_end 被調(diào)用后,在到達(dá)同步點(diǎn)之前執(zhí)行被執(zhí)行(線程在到達(dá)并行域的同步點(diǎn)之前需要保證所有的任務(wù)都被執(zhí)行完成)。
整個(gè)過(guò)程大致如下圖所示:

上面的 OpenMP task 程序?qū)?yīng)的反匯編程序如下所示:
00000000004008ad <main>: 4008ad: 55 push %rbp 4008ae: 48 89 e5 mov %rsp,%rbp 4008b1: ba 04 00 00 00 mov $0x4,%edx 4008b6: be 00 00 00 00 mov $0x0,%esi 4008bb: bf db 08 40 00 mov $0x4008db,%edi 4008c0: e8 8b fe ff ff callq 400750 <GOMP_parallel_start@plt> 4008c5: bf 00 00 00 00 mov $0x0,%edi 4008ca: e8 0c 00 00 00 callq 4008db <main._omp_fn.0> 4008cf: e8 8c fe ff ff callq 400760 <GOMP_parallel_end@plt> 4008d4: b8 00 00 00 00 mov $0x0,%eax 4008d9: 5d pop %rbp 4008da: c3 retq 00000000004008db <main._omp_fn.0>: 4008db: 55 push %rbp 4008dc: 48 89 e5 mov %rsp,%rbp 4008df: 48 83 ec 10 sub $0x10,%rsp 4008e3: 48 89 7d f8 mov %rdi,-0x8(%rbp) 4008e7: c7 04 24 00 00 00 00 movl $0x0,(%rsp) # 參數(shù) flags 4008ee: 41 b9 01 00 00 00 mov $0x1,%r9d # 參數(shù) if_clause 4008f4: 41 b8 01 00 00 00 mov $0x1,%r8d # 參數(shù) arg_align 4008fa: b9 00 00 00 00 mov $0x0,%ecx # 參數(shù) arg_size 4008ff: ba 00 00 00 00 mov $0x0,%edx # 參數(shù) cpyfn 400904: be 00 00 00 00 mov $0x0,%esi # 參數(shù) data 400909: bf 15 09 40 00 mov $0x400915,%edi # 這里就是調(diào)用函數(shù) main._omp_fn.1 40090e: e8 9d fe ff ff callq 4007b0 <GOMP_task@plt> 400913: c9 leaveq 400914: c3 retq 0000000000400915 <main._omp_fn.1>: 400915: 55 push %rbp 400916: 48 89 e5 mov %rsp,%rbp 400919: 48 83 ec 10 sub $0x10,%rsp 40091d: 48 89 7d f8 mov %rdi,-0x8(%rbp) 400921: e8 4a fe ff ff callq 400770 <omp_get_thread_num@plt> 400926: 89 c6 mov %eax,%esi 400928: bf d0 09 40 00 mov $0x4009d0,%edi 40092d: b8 00 00 00 00 mov $0x0,%eax 400932: e8 49 fe ff ff callq 400780 <printf@plt> 400937: c9 leaveq 400938: c3 retq 400939: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
從上面程序反匯編的結(jié)果我們可以知道,在主函數(shù)當(dāng)中仍然和之前一樣在并行域前后分別調(diào)用了 GOMP_parallel_start 和 GOMP_parallel_end,然后在兩個(gè)函數(shù)之間調(diào)用并行域的代碼 main._omp_fn.0 ,并行域當(dāng)中的代碼被編譯成函數(shù) main._omp_fn.0 ,從上面的匯編代碼我們可以看到在函數(shù) main._omp_fn.0 調(diào)用了函數(shù) GOMP_task ,這個(gè)函數(shù)的函數(shù)聲明如下所示:
void GOMP_task (void (*fn) (void *), void *data, void (*cpyfn) (void *, void *), long arg_size, long arg_align, bool if_clause, unsigned flags);
在這里我們重要解釋一下部分參數(shù),首先我們需要了解的是在 x86 當(dāng)中的函數(shù)調(diào)用規(guī)約,這一點(diǎn)我們?cè)谇懊娴奈恼庐?dāng)中已經(jīng)討論過(guò)了,這里只是說(shuō)明一下:
| 寄存器 | 含義 |
|---|---|
| rdi | 第一個(gè)參數(shù) |
| rsi | 第二個(gè)參數(shù) |
| rdx | 第三個(gè)參數(shù) |
| rcx | 第四個(gè)參數(shù) |
| r8 | 第五個(gè)參數(shù) |
| r9 | 第六個(gè)參數(shù) |
根據(jù)上面的寄存器和參數(shù)的對(duì)應(yīng)關(guān)系,在上面的匯編代碼當(dāng)中已經(jīng)標(biāo)注了對(duì)應(yīng)的參數(shù)。在這些參數(shù)當(dāng)中最重要的一個(gè)參數(shù)就是第一個(gè)函數(shù)指針,對(duì)應(yīng)的匯編語(yǔ)句為 mov $0x400915,%edi,可以看到的是傳入的函數(shù)的地址為 0x400915,根據(jù)上面的匯編程序可以知道這個(gè)地址對(duì)應(yīng)的函數(shù)就是 main._omp_fn.1,這其實(shí)就是 task 區(qū)域之間被編譯之后的對(duì)應(yīng)的函數(shù),從上面的 main._omp_fn.1 匯編程序當(dāng)中也可以看出來(lái)調(diào)用了函數(shù) omp_get_thread_num,這和前面的 task 區(qū)域當(dāng)中代碼是相對(duì)應(yīng)的。
現(xiàn)在我們來(lái)解釋一下其他的幾個(gè)參數(shù):
- fn,task 區(qū)域被編譯之后的函數(shù)地址。
- data,函數(shù) fn 的參數(shù)。
- cpyfn,參數(shù)拷貝函數(shù),一般是 NULL,有時(shí)候需要 task 當(dāng)中的數(shù)據(jù)不能是共享的,需要時(shí)私有的,這個(gè)時(shí)候可能就需要數(shù)據(jù)拷貝函數(shù),如果有數(shù)據(jù)需要及進(jìn)行拷貝而且這個(gè)參數(shù)還為 NULL 的話,那么在 OpenMP 內(nèi)部就會(huì)使用 memcpy 進(jìn)行內(nèi)存拷貝。
- arg_size,參數(shù)的大小。
- arg_align,參數(shù)多少字節(jié)對(duì)齊。
- if_clause,if 子句當(dāng)中的比較結(jié)果,如果沒(méi)有 if 字句的話就是 true 。
- flags,用于表示 task construct 的特征或者屬性,比如是否是最終任務(wù)。
我們現(xiàn)在使用另外一個(gè)例子,來(lái)看看參數(shù)傳遞的變化。
#include <stdio.h>
#include <omp.h>
int main()
{
#pragma omp parallel num_threads(4) default(none)
{
int data = omp_get_thread_num();
#pragma omp task default(none) firstprivate(data) if(data > 100)
{
data = omp_get_thread_num();
printf("data = %d Hello World from tid = %d\n", data, omp_get_thread_num());
}
}
return 0;
}
上面的程序被編譯之后對(duì)應(yīng)的匯編程序如下所示:
00000000004008ad <main>: 4008ad: 55 push %rbp 4008ae: 48 89 e5 mov %rsp,%rbp 4008b1: 48 83 ec 10 sub $0x10,%rsp 4008b5: ba 04 00 00 00 mov $0x4,%edx 4008ba: be 00 00 00 00 mov $0x0,%esi 4008bf: bf df 08 40 00 mov $0x4008df,%edi 4008c4: e8 87 fe ff ff callq 400750 <GOMP_parallel_start@plt> 4008c9: bf 00 00 00 00 mov $0x0,%edi 4008ce: e8 0c 00 00 00 callq 4008df <main._omp_fn.0> 4008d3: e8 88 fe ff ff callq 400760 <GOMP_parallel_end@plt> 4008d8: b8 00 00 00 00 mov $0x0,%eax 4008dd: c9 leaveq 4008de: c3 retq 00000000004008df <main._omp_fn.0>: 4008df: 55 push %rbp 4008e0: 48 89 e5 mov %rsp,%rbp 4008e3: 48 83 ec 20 sub $0x20,%rsp 4008e7: 48 89 7d e8 mov %rdi,-0x18(%rbp) 4008eb: e8 80 fe ff ff callq 400770 <omp_get_thread_num@plt> 4008f0: 89 45 fc mov %eax,-0x4(%rbp) 4008f3: 83 7d fc 64 cmpl $0x64,-0x4(%rbp) 4008f7: 0f 9f c2 setg %dl 4008fa: 8b 45 fc mov -0x4(%rbp),%eax 4008fd: 89 45 f0 mov %eax,-0x10(%rbp) 400900: 48 8d 45 f0 lea -0x10(%rbp),%rax 400904: c7 04 24 00 00 00 00 movl $0x0,(%rsp) # 參數(shù) flags 40090b: 41 89 d1 mov %edx,%r9d # 參數(shù) if_clause 40090e: 41 b8 04 00 00 00 mov $0x4,%r8d # 參數(shù) arg_align 400914: b9 04 00 00 00 mov $0x4,%ecx # 參數(shù) arg_size 400919: ba 00 00 00 00 mov $0x0,%edx # 參數(shù) cpyfn 40091e: 48 89 c6 mov %rax,%rsi # 參數(shù) data 400921: bf 2d 09 40 00 mov $0x40092d,%edi # 這里就是調(diào)用函數(shù) main._omp_fn.1 400926: e8 85 fe ff ff callq 4007b0 <GOMP_task@plt> 40092b: c9 leaveq 40092c: c3 retq 000000000040092d <main._omp_fn.1>: 40092d: 55 push %rbp 40092e: 48 89 e5 mov %rsp,%rbp 400931: 48 83 ec 20 sub $0x20,%rsp 400935: 48 89 7d e8 mov %rdi,-0x18(%rbp) 400939: 48 8b 45 e8 mov -0x18(%rbp),%rax 40093d: 8b 00 mov (%rax),%eax 40093f: 89 45 fc mov %eax,-0x4(%rbp) 400942: e8 29 fe ff ff callq 400770 <omp_get_thread_num@plt> 400947: 89 c2 mov %eax,%edx 400949: 8b 45 fc mov -0x4(%rbp),%eax 40094c: 89 c6 mov %eax,%esi 40094e: bf f0 09 40 00 mov $0x4009f0,%edi 400953: b8 00 00 00 00 mov $0x0,%eax 400958: e8 23 fe ff ff callq 400780 <printf@plt> 40095d: c9 leaveq 40095e: c3 retq 40095f: 90 nop
在上面的函數(shù)當(dāng)中我們將 data 一個(gè) 4 字節(jié)的數(shù)據(jù)作為線程私有數(shù)據(jù),可以看到給函數(shù) GOMP_task 傳遞的參數(shù)參數(shù)的大小以及參數(shù)的內(nèi)存對(duì)齊大小都發(fā)生來(lái)變化,從原來(lái)的 0 變成了 4,這因?yàn)?int 類型數(shù)據(jù)占 4 個(gè)字節(jié)。
Task Construct 源碼分析
在本小節(jié)當(dāng)中主要談?wù)撛?OpenMP 內(nèi)部是如何實(shí)現(xiàn) task 的,關(guān)于這一部分內(nèi)容設(shè)計(jì)的內(nèi)容還是比較龐雜,首先需要了解的是在 OpenMP 當(dāng)中使用 task construct 的被稱作顯示任務(wù)(explicit task),這種任務(wù)在 OpenMP 當(dāng)中會(huì)有兩個(gè)任務(wù)隊(duì)列(雙向循環(huán)隊(duì)列),將所有的任務(wù)都保存在這樣一張列表當(dāng)中,整體結(jié)構(gòu)如下圖所示:

在上圖當(dāng)中由同一個(gè)線程創(chuàng)建的任務(wù)為 child_task,他們之間使用 next_child 和 prev_child 兩個(gè)指針進(jìn)行連接,不同線程創(chuàng)建的任務(wù)之間可以使用 next_queue 和 prev_queue 兩個(gè)指針進(jìn)行連接。
任務(wù)的結(jié)構(gòu)體描述如下所示:
struct gomp_task
{
struct gomp_task *parent; // 任務(wù)的父親任務(wù)
struct gomp_task *children; // 子任務(wù)
struct gomp_task *next_child; // 下一個(gè)子任務(wù)
struct gomp_task *prev_child; // 上一個(gè)子任務(wù)
struct gomp_task *next_queue; // 下一個(gè)任務(wù) (不一定是同一個(gè)線程創(chuàng)建的子任務(wù))
struct gomp_task *prev_queue; // 上一個(gè)任務(wù) (不一定是同一個(gè)線程創(chuàng)建的子任務(wù))
struct gomp_task_icv icv; // openmp 當(dāng)中內(nèi)部全局設(shè)置使用變量的值(internal control variable)
void (*fn) (void *); // task construct 被編譯之后的函數(shù)
void *fn_data; // 函數(shù)參數(shù)
enum gomp_task_kind kind; // 任務(wù)類型 具體類型如下面的枚舉類型
bool in_taskwait; // 是否處于 taskwait 狀態(tài)
bool in_tied_task; // 是不是在綁定任務(wù)當(dāng)中
bool final_task; // 是不是最終任務(wù)
gomp_sem_t taskwait_sem; // 對(duì)象鎖 用于保證線程操作這個(gè)數(shù)據(jù)的時(shí)候的線程安全
};
// openmp 當(dāng)中的任務(wù)的狀態(tài)
enum gomp_task_kind
{
GOMP_TASK_IMPLICIT,
GOMP_TASK_IFFALSE,
GOMP_TASK_WAITING,
GOMP_TASK_TIED
};
在了解完上面的數(shù)據(jù)結(jié)構(gòu)之后我們來(lái)看一下前面的給 OpenMP 內(nèi)部提交任務(wù)的函數(shù) GOMP_task,其源代碼如下所示:
/* Called when encountering an explicit task directive. If IF_CLAUSE is
false, then we must not delay in executing the task. If UNTIED is true,
then the task may be executed by any member of the team. */
void
GOMP_task (void (*fn) (void *), void *data, void (*cpyfn) (void *, void *),
long arg_size, long arg_align, bool if_clause, unsigned flags)
{
struct gomp_thread *thr = gomp_thread ();
// team 是 OpenMP 一個(gè)線程組當(dāng)中共享的數(shù)據(jù)
struct gomp_team *team = thr->ts.team;
#ifdef HAVE_BROKEN_POSIX_SEMAPHORES
/* If pthread_mutex_* is used for omp_*lock*, then each task must be
tied to one thread all the time. This means UNTIED tasks must be
tied and if CPYFN is non-NULL IF(0) must be forced, as CPYFN
might be running on different thread than FN. */
if (cpyfn)
if_clause = false;
if (flags & 1)
flags &= ~1;
#endif
// 這里表示如果是 if 子句的條件為真的時(shí)候或者是孤立任務(wù)(team == NULL )或者是最終任務(wù)的時(shí)候或者任務(wù)隊(duì)列當(dāng)中的任務(wù)已經(jīng)很多的時(shí)候
// 提交的任務(wù)需要立即執(zhí)行而不能夠放入任務(wù)隊(duì)列當(dāng)中然后在 GOMP_parallel_end 函數(shù)當(dāng)中進(jìn)行任務(wù)的取出
// 再執(zhí)行
if (!if_clause || team == NULL
|| (thr->task && thr->task->final_task)
|| team->task_count > 64 * team->nthreads)
{
struct gomp_task task;
gomp_init_task (&task, thr->task, gomp_icv (false));
task.kind = GOMP_TASK_IFFALSE;
task.final_task = (thr->task && thr->task->final_task) || (flags & 2);
if (thr->task)
task.in_tied_task = thr->task->in_tied_task;
thr->task = &task;
if (__builtin_expect (cpyfn != NULL, 0))
{
// 這里是進(jìn)行數(shù)據(jù)的拷貝
char buf[arg_size + arg_align - 1];
char *arg = (char *) (((uintptr_t) buf + arg_align - 1)
& ~(uintptr_t) (arg_align - 1));
cpyfn (arg, data);
fn (arg);
}
else
// 如果不需要進(jìn)行數(shù)據(jù)拷貝則直接執(zhí)行這個(gè)函數(shù)
fn (data);
/* Access to "children" is normally done inside a task_lock
mutex region, but the only way this particular task.children
can be set is if this thread's task work function (fn)
creates children. So since the setter is *this* thread, we
need no barriers here when testing for non-NULL. We can have
task.children set by the current thread then changed by a
child thread, but seeing a stale non-NULL value is not a
problem. Once past the task_lock acquisition, this thread
will see the real value of task.children. */
if (task.children != NULL)
{
gomp_mutex_lock (&team->task_lock);
gomp_clear_parent (task.children);
gomp_mutex_unlock (&team->task_lock);
}
gomp_end_task ();
}
else
{
// 下面就是將任務(wù)先提交到任務(wù)隊(duì)列當(dāng)中然后再取出執(zhí)行
struct gomp_task *task;
struct gomp_task *parent = thr->task;
char *arg;
bool do_wake;
task = gomp_malloc (sizeof (*task) + arg_size + arg_align - 1);
arg = (char *) (((uintptr_t) (task + 1) + arg_align - 1)
& ~(uintptr_t) (arg_align - 1));
gomp_init_task (task, parent, gomp_icv (false));
task->kind = GOMP_TASK_IFFALSE;
task->in_tied_task = parent->in_tied_task;
thr->task = task;
// 這里就是參數(shù)拷貝邏輯 如果存在拷貝函數(shù)就通過(guò)拷貝函數(shù)進(jìn)行參數(shù)賦值 否則使用 memcpy 進(jìn)行
// 參數(shù)的拷貝
if (cpyfn)
cpyfn (arg, data);
else
memcpy (arg, data, arg_size);
thr->task = parent;
task->kind = GOMP_TASK_WAITING;
task->fn = fn;
task->fn_data = arg;
task->in_tied_task = true;
task->final_task = (flags & 2) >> 1;
// 在這里獲取全局隊(duì)列鎖 保證下面的代碼在多線程條件下的線程安全
// 因?yàn)樵谙旅娴拇a當(dāng)中會(huì)對(duì)全局的隊(duì)列進(jìn)行修改操作 下面的操作就是隊(duì)列的一些基本操作啦
gomp_mutex_lock (&team->task_lock);
if (parent->children)
{
task->next_child = parent->children;
task->prev_child = parent->children->prev_child;
task->next_child->prev_child = task;
task->prev_child->next_child = task;
}
else
{
task->next_child = task;
task->prev_child = task;
}
parent->children = task;
if (team->task_queue)
{
task->next_queue = team->task_queue;
task->prev_queue = team->task_queue->prev_queue;
task->next_queue->prev_queue = task;
task->prev_queue->next_queue = task;
}
else
{
task->next_queue = task;
task->prev_queue = task;
team->task_queue = task;
}
++team->task_count;
gomp_team_barrier_set_task_pending (&team->barrier);
do_wake = team->task_running_count + !parent->in_tied_task
< team->nthreads;
gomp_mutex_unlock (&team->task_lock);
if (do_wake)
gomp_team_barrier_wake (&team->barrier, 1);
}
}
對(duì)于上述所討論的內(nèi)容大家只需要了解相關(guān)的整體流程即可,細(xì)節(jié)除非你是 openmp 的開(kāi)發(fā)人員,否則事實(shí)上沒(méi)有多大用,大家只需要了解大致過(guò)程即可,幫助你進(jìn)一步深入理解 OpenMP 內(nèi)部的運(yùn)行機(jī)制。
但是需要了解的是上面的整個(gè)過(guò)程還只是將任務(wù)提交到 OpenMP 內(nèi)部的任務(wù)隊(duì)列當(dāng)中,還沒(méi)有執(zhí)行,我們?cè)谇懊嬲劦竭^(guò)在線程執(zhí)行完并行域的代碼會(huì)執(zhí)行函數(shù) GOMP_parallel_end 在這個(gè)函數(shù)內(nèi)部還會(huì)調(diào)用其他函數(shù),最終會(huì)調(diào)用函數(shù) gomp_barrier_handle_tasks 將內(nèi)部的所有的任務(wù)執(zhí)行完成。
void
gomp_barrier_handle_tasks (gomp_barrier_state_t state)
{
struct gomp_thread *thr = gomp_thread ();
struct gomp_team *team = thr->ts.team;
struct gomp_task *task = thr->task;
struct gomp_task *child_task = NULL;
struct gomp_task *to_free = NULL;
// 首先對(duì)全局的隊(duì)列結(jié)構(gòu)進(jìn)行加鎖操作
gomp_mutex_lock (&team->task_lock);
if (gomp_barrier_last_thread (state))
{
if (team->task_count == 0)
{
gomp_team_barrier_done (&team->barrier, state);
gomp_mutex_unlock (&team->task_lock);
gomp_team_barrier_wake (&team->barrier, 0);
return;
}
gomp_team_barrier_set_waiting_for_tasks (&team->barrier);
}
while (1)
{
if (team->task_queue != NULL)
{
struct gomp_task *parent;
// 從任務(wù)隊(duì)列當(dāng)中拿出一個(gè)任務(wù)
child_task = team->task_queue;
parent = child_task->parent;
if (parent && parent->children == child_task)
parent->children = child_task->next_child;
child_task->prev_queue->next_queue = child_task->next_queue;
child_task->next_queue->prev_queue = child_task->prev_queue;
if (child_task->next_queue != child_task)
team->task_queue = child_task->next_queue;
else
team->task_queue = NULL;
child_task->kind = GOMP_TASK_TIED;
team->task_running_count++;
if (team->task_count == team->task_running_count)
gomp_team_barrier_clear_task_pending (&team->barrier);
}
gomp_mutex_unlock (&team->task_lock);
if (to_free) // 釋放任務(wù)的內(nèi)存空間 to_free 在后面會(huì)被賦值成 child_task
{
gomp_finish_task (to_free);
free (to_free);
to_free = NULL;
}
if (child_task) // 調(diào)用任務(wù)對(duì)應(yīng)的函數(shù)
{
thr->task = child_task;
child_task->fn (child_task->fn_data);
thr->task = task;
}
else
return; // 退出 while 循環(huán)
gomp_mutex_lock (&team->task_lock);
if (child_task)
{
struct gomp_task *parent = child_task->parent;
if (parent)
{
child_task->prev_child->next_child = child_task->next_child;
child_task->next_child->prev_child = child_task->prev_child;
if (parent->children == child_task)
{
if (child_task->next_child != child_task)
parent->children = child_task->next_child;
else
{
/* We access task->children in GOMP_taskwait
outside of the task lock mutex region, so
need a release barrier here to ensure memory
written by child_task->fn above is flushed
before the NULL is written. */
__atomic_store_n (&parent->children, NULL,
MEMMODEL_RELEASE);
if (parent->in_taskwait)
gomp_sem_post (&parent->taskwait_sem);
}
}
}
gomp_clear_parent (child_task->children);
to_free = child_task;
child_task = NULL;
team->task_running_count--;
if (--team->task_count == 0
&& gomp_team_barrier_waiting_for_tasks (&team->barrier))
{
gomp_team_barrier_done (&team->barrier, state);
gomp_mutex_unlock (&team->task_lock);
gomp_team_barrier_wake (&team->barrier, 0);
gomp_mutex_lock (&team->task_lock);
}
}
}
}
總結(jié)
在本篇文章當(dāng)中主要給大家介紹了,OpenMP 內(nèi)部對(duì)于任務(wù)的處理流程,這其中的細(xì)節(jié)非常復(fù)雜,大家只需要了解它的整個(gè)工作流程即可,這已經(jīng)能夠幫助大家理清楚整個(gè) OpenMP 內(nèi)部是如何對(duì)任務(wù)進(jìn)行處理的,如果大家感興趣可以自行研讀源程序。
更多精彩內(nèi)容合集可訪問(wèn)項(xiàng)目:github.com/Chang-LeHun…
以上就是OpenMP task construct 實(shí)現(xiàn)原理及源碼示例解析的詳細(xì)內(nèi)容,更多關(guān)于OpenMP task construct原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
C語(yǔ)言實(shí)現(xiàn)學(xué)生考勤系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)學(xué)生考勤系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
C語(yǔ)言中進(jìn)行大小寫(xiě)字母轉(zhuǎn)化的示例代碼
C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中提供了用于大小寫(xiě)轉(zhuǎn)換的函數(shù),使得這一操作變得簡(jiǎn)單而高效,本文將詳細(xì)介紹如何在C語(yǔ)言中進(jìn)行大小寫(xiě)字母的轉(zhuǎn)換,包括相關(guān)的函數(shù)和示例代碼,需要的朋友可以參考下2024-03-03
c++使用Easyx圖形庫(kù)實(shí)現(xiàn)飛機(jī)大戰(zhàn)
本文詳細(xì)講解了c++使用Easyx圖形庫(kù)實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
C語(yǔ)言正則表達(dá)式詳解 regcomp() regexec() regfree()用法詳解
C語(yǔ)言處理正則表達(dá)式常用的函數(shù)有regcomp()、regexec()、regfree()和regerror(),這里就為大家介紹一下,需要的朋友可以參考一下啊2018-04-04
C++實(shí)踐排序函數(shù)模板項(xiàng)目的參考方法
今天小編就為大家分享一篇關(guān)于C++實(shí)踐排序函數(shù)模板項(xiàng)目的參考方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02

