精华内容
下载资源
问答
  • 多核程序设计

    千次阅读 2019-07-04 12:46:01
    2.掌握Microsoft Visual Studio 编写编译简单OpenMP程序的方法。 二. 实验内容: 1.配置visual studio 2012使支持openMP;配置环境变量,确定线程的数目为4(有3种配置工作线程的方法,分别掌握)。P26—30 2....

    一. 实验目的:
    1.掌握Microsoft Visual Studio 2012配置OpenMP的方法;
    2.掌握Microsoft Visual Studio 编写编译简单OpenMP程序的方法。
    二. 实验内容:
    1.配置visual studio 2012使支持openMP;配置环境变量,确定线程的数目为4(有3种配置工作线程的方法,分别掌握)。P26—30
    2.使用parallel指令多线程并行程序实现如下文字的输出:51
    Hello, openMP!当前工作线程号为?,当前工作总线程数为2
    Hello, openMP!当前工作线程号为?,当前工作总线程数为2
    I’m XXX.当前工作线程号为0,当前工作总线程数为1
    We will become friends. 当前工作线程号为?,当前工作总线程数为3
    We will become friends. 当前工作线程号为?,当前工作总线程数为3
    We will become friends. 当前工作线程号为?,当前工作总线程数为3
    3.使用parallel for指令求z[i] = x[i]+y[i],输出z[i]。P72
    4. 比较运行时间差别;对比输出结果。 P4,P5
    5. 使用sections和section,及parallel for两种方式多线程输出100以内的所有素数。p79

    2.源代码
    #include <stdio.h>
    #include <omp.h>
    int main() {
    #pragma omp parallel num_threads(2) //4个工作线程
    {
    printf(“Hello, openMP!当前工作线程号为 = %d”,omp_get_thread_num());
    printf(“当前工作总线程数为 = %d\n”, omp_get_num_threads());
    }
    return 0;}

    结果截图:

    2-2源代码:
    #include"stdafx.h"
    #include <stdio.h>
    #include <omp.h>
    int main() {

    printf("I'm RenHui当前工作线程号为 = %d",omp_get_thread_num()); 
    printf("当前工作总线程数为= %d\n", omp_get_num_threads()); 
    

    printf(“并行区开始\n”);
    return 0;}

    截图:

    2-3
    源代码:
    #include"stdafx.h"
    #include <stdio.h>
    #include <omp.h>
    int main() {
    #pragma omp parallel num_threads(3)
    {printf(“We will become friends. 当前工作线程号为 = %d\t,”,omp_get_thread_num());
    printf(“当前工作总线程数为= %d\n”, omp_get_num_threads());

    }
    return 0;}
    结果解图

    3.0
    源代码:
    #include"stdafx.h"
    #include <stdio.h>
    #include <omp.h>
    int main() {
    int z[3];
    int x[3]={1,2,3};
    int y[3]={8,5.2};
    #pragma omp parallel for
    for(int i=0;i<3;i++){
    z[i]=x[i]+y[i];
    printf("%d",z[i]);
    }
    return 0;
    }
    结果截图:

    展开全文
  • 多核程序设计技术 通过软件多线程提升性能 电子书 PDF格式 带书签 高清 作者都是长期供职于Intel公司的资深软件工程师和结构师,书中融入了他们自己丰富的软硬件开发经验,可以为面向多核体系结构进行并行程序设计...
  • 这是大学的多核程序设计课程内容,所以在这里就简单的总结一下。如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,它非常的初级。 首先说明一下,本人在Linux虚拟机上编写多线程程序,包含头文件 #...

    这是大学的多核程序设计课程内容,所以在这里就简单的总结一下。如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,它非常的初级。

    首先说明一下,本人在Linux虚拟机上编写多线程程序,包含头文件

    #include <pthread.h> 
    

    一、线程的创建

    在Linux下创建的线程的API接口是pthread_create(),它的完整定义是:

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 
    

    1 . 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。
    2 . 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。
    3 . 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。如果线程创建成功,这个接口会返回0。
    4 . 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据

    • 线程对象:存储线程信息,对用户不透明
    • 线程对象的数据类型:pthread_t
    • 创建线程:pthread_create

    二、等待函数

    int pthread_join( pthread_t  thread, void** ret_val_ p);
    

    pthread_join()这个接口的第一个参数就是新创建线程的句柄了,而第二个参数就会去接受线程的返回值。pthread_join()接口会阻塞主进程的执行,直到合并的线程执行结束。由于线程在结束之后会将0返回给系统,那么pthread_join()获得的线程返回值自然也就是0。

    小例子(代码抄的,我解读)

    #include <stdio.h>  
    #include <pthread.h>  
    void* thread( void *arg )  
    {  
       printf( "This is a thread and arg = %d.\n", *(int*)arg); 
      //将arg强制类型转换为指针,指向变量的值 
        *(int*)arg = 0;  
        return arg;  
    }  
    int main( int argc, char *argv[] )  
    {  
        pthread_t th;  
        int ret;  
        int arg = 10;  
        int *thread_ret = NULL;  
        ret = pthread_create( &th, NULL, thread, &arg ); 
        //成功返回0 
        if( ret != 0 )
        {  
            printf( "Create thread error!\n");  
            return -1;  
        }  
        printf( "This is the main process.\n" );  
        pthread_join( th, (void**)&thread_ret );  
       //二级指针强制类型转换,而后引用
        printf( "thread_ret = %d.\n", *thread_ret );  
        return 0;  
    }  
    

    三、线程间的通信和同步

    虽然线程本地存储可以避免线程访问共享数据,但是线程之间的大部分数据始终还是共享的。在涉及到对共享数据进行读写操作时,就必须使用同步机制,Linux提供的线程同步机制主要有互斥锁和条件变量。

    临界区

    临界区:当多个线程访问共享数据时,为保正数据的完整性,将共享数据保护起来

    访问临界区的原则:

    1. 一次最多只能一个线程停留在临界区内;
    2. 不能让一个线程无限地停留在临界区内。

    忙等待

    flag = 0;   主线程初始化
    ………..
    y = Compute(my_rank);
    while (flag != my_rank);
    x = x + y;
    flag++;
    

    执行临界区代码的顺序:线程0,线程1,线程2…

    互斥量

    互斥量:限制每次只有一个线程能进入临界区。
    互斥量数据类型:pthread_mutex_t

    互斥量初始化
    int pthread_mutex_init(pthread_mutex_t∗ mutex_p,
    const pthread_mutexattr_t∗ attr_p);
    释放互斥量
    int pthread_mutex_destroy(pthread_mutex_t∗ mutex_p);
    加锁(阻塞和非阻塞)
    int pthread_mutex_lock(pthread_mutex_t∗ mutex_p );
    int pthread_mutex_trylock(pthread_mutex_t *mutex_p)
    解锁
    int pthread_mutex_unlock(pthread_mutex_t∗ mutex_p );

    当线程数多于核数时候,忙等待效率降低。
    忙等待:强调访问临界区的顺序
    互斥量:访问临界区的顺序随机的

    多个矩阵相乘

    voidThread_work(void∗ rank) 
    {
          long my_rank = (long) rank;
          matrix_t my_mat = Allocate_matrix(n);
          Generate_matrix(my_mat);
          pthread_mutex_lock(&mutex);
          Multiply_matrix(product_mat, my_mat);
          pthread_mutex_unlock(&mutex);
          Free_matrix(&my_mat);
          return NULL;
    }
    

    使用互斥量和忙等待来实现路障的方法;

    使用一个通过互斥量保护的计数器;

    当计数器表明,所有线程都进入过临界区, 线程就可以离开了。

    信号量

    信号量可以用于临界区,保护共享资源。
    信号量的特性如下:

    • 信号量有一个非负整数
    • 要访问共享资源的 线程必须获取一个信号量,则信号量减1。
    • 当信号量为0时,试图访问共享资源的线程将处于等待状态。
    • 离开共享资源的 线程释放信号量,则信号量加1。

    信号量可以认为是一种特殊类型的 unsigned int 无符号整型变量,可以赋值为 0,1,2,3 等,一般只赋0(对应上锁的互斥量)/1(未上锁的互斥量)。要把一个二元互斥量用作互斥量时候=,需要把信号量的值初始化为
    1,即开锁状态。在要保护的临界区前调用函数 sem_wait,线程执行到 sem_wait 函数时,如果信号量为 0,线程就会被阻塞,否则减1 后进去临界区。执行完临界区的操作后,再调用 sem_post 对信号量的值加 1,使得在 sem_wait中阻塞的其他线程能够继续运行。

    void* Send_msg(void* rank)
    {  
    	long my_rank = (long) rank;  
    	long dest = (my_rank + 1) % thread_count;  
    	char∗  my_msg = malloc(MSG_MAX∗sizeof(char));    			
    	sprintf(my_msg, "Hello to %ld from %ld", dest, my_rank);  	  
    	messages[dest] = my_msg;  
    	sem_post(&semaphores[dest])//信号量为 0,线程就会被阻塞,否则减1 后进去临界区。				       
    	sem_wait(&semaphores[my_rank]);  
    	printf("Thread %ld > %s n", my_rank, messages[my_rank]);     return NULL;
    } 
    

    不同信号量的语法

    int sem_init(sem_t∗ semaphore_p, int shared, unsigned initial_val );
    int sem_destroy(sem_t∗ semaphore_p);
    int sem_post(sem_t∗ semaphore_p); 
    int sem_wait(sem_t∗ semaphore_p);
    

    注意:信号量不是 Pthreads 线程库的一部分,所以在使用信号量的程序开头加头文件

    #include <semaphore.h>
    

    路障

    作用

    使线程之间同步,并保证它们运行到了同一个位置。没有线程可以越过设置的路障,直到所有线程都抵达这里。

    使用忙等待和互斥量实现路障

    #include <stdio.h>
    #include <pthread.h>
    #pragma comment(lib, "pthreadVC2.lib")
    
    const int thread = 8;
    int count;
    pthread_mutex_t pmt;
    
    void* work(void* rank)
    {
        const long long localRank = (long long)rank, dest = (localRank + 1) % thread;
        pthread_mutex_lock(&pmt);   // 进入读写区,上锁,计数器加一,解锁
        printf("Thread %2d reached the barrier.\n", localRank); fflush(stdout);
        count++;
        pthread_mutex_unlock(&pmt);
        while (count < thread);     // 使用忙等待来等所有的线程都达到栅栏
        printf("Thread %2d passed the barrier.\n", localRank); fflush(stdout);
        return ;
    }
    
    int main()
    {
        pthread_t pth[thread];
        int i;
        long long list[thread];
        pthread_mutex_init(&pmt, NULL);
        for (i = count = 0; i < thread; i++)
        {
            list[i] = i;
            pthread_create(&pth[i], NULL, work, (void *)list[i]);
        }
        for (i = 0; i < thread; i++)
            pthread_join(pth[i], NULL);
        pthread_mutex_destroy(&pmt);
        printf("\nfinish.\n");
        getchar();
        return 0;
    }
    

    条件变量

    一个条件变量允许停止一个线程,直到某个事件发生;当条件被满足时,另一个线程可以激活这个线程;

    条件变量总是和互斥量绑在一起。

    条件变量数据类型:pthread_cond_t

    条件变量初始化

    int pthread_cond_init(pthread_cond_t* cond_var_p, const pthread_condattr_t* attr)
    

    释放条件变量

    int pthread_cond_destroy (pthread_cond_t* cond_var_p)
    

    解锁一个阻塞的线程

    int pthread_cond_signal(pthread_cond_t∗ cond_var_p);
    

    解锁所有被阻塞的线程

    int pthread_cond_broadcast(pthread_cond_t∗ cond_var_p);
    

    通过互斥量阻塞线程

    int pthread_cond_wait(pthread_cond_t∗ cond_var_p,pthread_mutex_t∗ mutex_p);
    

    使用条件变量来实现路障

    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    #pragma comment(lib, "pthreadVC2.lib")
    
    const int thread = 8;
    int count;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    
    void* work(void* rank)
    {
        const long long localRank = (long long)rank, dest = (localRank + 1) % thread;
        printf("Thread %2d reached the barrier.\n", localRank); fflush(stdout);
        pthread_mutex_lock(&mutex);         // 上锁
        count++;
        if (count == thread)                // 最后一个进入的线程
        {
            count = 0;                      // 计数器清零
            pthread_cond_broadcast(&cond);  // 广播所有线程继续向下执行
        }
        else
            for (; pthread_cond_wait(&cond, &mutex) != 0;);// 等待其他线程
        pthread_mutex_unlock(&mutex);       // 条件变量阻塞解除后会自动将互斥量上锁,需要手工解除
    
        printf("Thread %2d passed the barrier.\n", localRank); fflush(stdout);
        return ;
    }
    
    int main()
    {
        pthread_t pth[thread];
        int i;
        long long list[thread];
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);
        for (i = count = 0; i < thread; i++)
        {
            list[i] = i;
            pthread_create(&pth[i], NULL, work, (void *)list[i]);
        }
        for (i = 0; i < thread; i++)
            pthread_join(pth[i], NULL);
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);
        printf("\nfinish.\n");
        getchar();
        return 0;
    }
    

    四、读写锁

    读写锁有点像互斥量,但提供两个方法。

    第 1 个用来对读上锁,而第 2 个用来对写上锁;

    很多线程都可以获得读锁,但只有一个线程可以获得写锁。

    如果有线程获得了读锁,那么其他线程无法获得写锁。

    初始化
    int pthread_rwlock_init(pthread_rwlock_t∗ rwlock_p,
    const pthread_rwlockattr_t∗ attr_p );
    读加锁
    int pthread_rwlock_rdlock(pthread_rwlock_t∗ rwlock_p);
    写加锁
    int pthread_rwlock_wrlock(pthread_rwlock_t∗ rwlock_p);
    解锁
    int pthread_rwlock_unlock(pthread_rwlock_t∗ rwlock_p);
    销毁
    int pthread_rwlock_destroy(pthread_rwlock_t∗ rwlock_p );
    

    详细例子

    展开全文
  • 多核程序设计技术-通过软件多线程提升性能(中文版)
  • 阅读以下关于嵌入式多核程序设计技术的描述,回答问题 1 至问题 3。 【说明】 近年来,多核技术己被广泛应用于众多安全关键领域(如:航空航天等)的电子设备中,面向多核技术的并行程序设计方法已成为软件人员急需...

    试题三(共 25 )

    阅读以下关于嵌入式多核程序设计技术的描述,回答问题 1 至问题 3。

    【说明】

    近年来,多核技术己被广泛应用于众多安全关键领域(如:航空航天等)的电子设备中,面向多核技术的并行程序设计方法已成为软件人员急需掌握的主要技能之一。某宇航公司长期从事宇航电子设备的研制工作,随着宇航装备能力需求的提升,急需采用多核技术以增强设备的运算能力、降低功耗与体积,快速实现设备的升级与换代。针对面向多核开发,王工认为多核技术是对用户程序透明的,开发应把重点放在多核硬件架构和硬件模块设计上面,而软件方面,仅仅需要选择一款支持多核处理器的操作系统即可。而李工认为,多核架构能

    够使现有的软件更高效地运行,构建一个完善的软件架构是非常必要的。提高多核的利用率不能仅靠操作系统,还要求软件开发人员在程序设计 中考虑多进程或者多线程并行处理的编程问题。

    【问题 1(12 分)

    请用 300 字以内文字说明什么是多核技术和多线程技术,并回答李工的意见是否正确,为什么?

    【问题 2(6 分)

    在多核环境下,线程的活动有并行和并发两种方式,请用 300 字以内的文字说明这两种方式的含义及差别。

    【问题 3(7 分)

    请根据自己所掌握的多核、多线程的知识,判别表 3-1 给出的说法是否正确,并将答案写在答题纸上对应空白处(填写正确或错误)。

     

    转载于:https://www.cnblogs.com/jianfeijiang/p/10760623.html

    展开全文
  • OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。 执行模式 OpenMP采用fork-join...

    一、基本概念

    OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。

    执行模式

    OpenMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。

    OpenMP编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是编译指导语句API函数集环境变量

    编译指导语句

    #pragma omp directive_name [clause, ...]

    #pragma omp directive-name [clause, …]
    指导指令前缀所有的OpenMP语句都有前缀。 指导指令。每个语句都有一个指导指令,指导编译器并行化 子句,用来控制具体行为,任选、无序。

    并行区域与任务分配

    并行区域(创建线程): parallel
    任务分配: for,sections,single,master

    在这里插入图片描述

    二、编译器指令

    parallel命令

    用来构造一个并行域,并行域中的代码被所有的线程执行.
    格式 : #pragma omp parallel [子句[子句]…]

    创建一组线程,主线程的线程号为0。
    从并行域开始,代码被复制并被所有线程执行。
    并行域结束时有个隐式路障,只有主线程能在此之后继续执行。

    int main()
    {
    #pragma omp parallel
    	{
    		printf("hello world!\n");
    	}
    }
    

    指定线程数量

    num_threads子句的设置
    omp_set_num_threads() 库函数
    OMP_NUM_THREADS 环境变量
    默认——计算机的逻辑CPU数量。

    int main()
    {
    #pragma omp parallel num_threads(8)
    	{
    		printf("hello world!, ThreadId = % d\n", omp_get_thread_num());
    	}
    }
    
    

    if子句

    if子句的值来决定是否并行执行。

    int main()
    {
    	int n = 12;
        #pragma omp parallel if(n>10) num_threads(2)
    	{
    		printf("if clause, ThreadId = % d\n", omp_get_thread_num());
    	}
    }
    

    动态设置线程数量

    计算线程数量:

    1、每个线程的循环次数较少时,线程创建代价较大;
    2、当线程数量远大于CPU数量时,将产生大量的线程切换、
    调度。

    每个线程运行的循环次数不低于4次。总的运行线程数最大不超过2倍CPU核数。

    const int MIN_ITERATOR_NUM = 4;
    const int g_ncore = omp_get_num_procs(); //获取执行核的数量
    int dtn(int n, int min_n)
    {
    	int max_tn = n / min_n;
    	int tn = max_tn > g_ncore ? g_ncore : max_tn; 
    	//tn表示要设置的线程数量
    	if (tn < 1)
    	{
    		tn = 1;
    	}
    	return tn;
    }
    int main()
    {
    	int n = 50;
    	int max_tn = n / MIN_ITERATOR_NUM;
    	int tn = max_tn > 2 *g_ncore ? 2 * g_ncore : max_tn;//tn表示要设置的线程数量
    #pragma omp parallel for num_threads(dtn(n, MIN_ITERATOR_NUM))
    	for (int i = 0; i < n; i++)
    	{
    		printf("Thread Id = %ld\n", omp_get_thread_num());
    		//Do some work here
    	}
    }
    

    for和parallel for命令

    for指令:将一个for循环分配到多个线程中执行;
    for指令可以和parallel合并使用,parallel for;
    for指令也可以单独用在parallel语句的并行域中。
    for结构有隐式路障

    格式

    #pragma omp [parallel] for [子句]
    for循环语句

    parallel与for分离的版本:

    int main()
    {
    	int j = 0;
    #pragma omp parallel
    	{
    #pragma omp for
    		for (j = 0; j < 4; j++)
    		{
    			printf("j = % d, ThreadId = % d\n", j, omp_get_thread_num());
    		}
    	}
    
    }
    

    parallel 将紧跟的程序块扩展为若干相同的并行区域

    for 将循环中工作分配到线程组中

    parallel for 版本:

    #pragma omp parallel for
    	for(int  i = 0; i < 10; i++)
    	{
    		printf("i = % d, ThreadId = % d\n", i, omp_get_thread_num());
    	}
    

    外层循环并行化

    int main()
    {
    	int i, j;
    	omp_set_num_threads(4);
    #pragma omp parallel for private(j)
    	for (i = 0; i < 2; i++)
    	{
    
    		for (j = 0; j < 6; j++)
    		{
    			printf("i = % d,j = % d, ThreadId = % d\n", i,j, omp_get_thread_num());
    		}
    	}
    }
    

    i = 0,j = 0, ThreadId = 0
    i = 0,j = 1, ThreadId = 0
    i = 0,j = 2, ThreadId = 0
    i = 0,j = 3, ThreadId = 0
    i = 0,j = 4, ThreadId = 0
    i = 0,j = 5, ThreadId = 0
    i = 1,j = 0, ThreadId = 1
    i = 1,j = 1, ThreadId = 1
    i = 1,j = 2, ThreadId = 1
    i = 1,j = 3, ThreadId = 1
    i = 1,j = 4, ThreadId = 1
    i = 1,j = 5, ThreadId = 1

    我们可以看到外部循环的线程ID一致,这就是外层循环并行化。

    内部循环并行化

    将指令语句移动至内循环即可

    int main()
    {
    	int i, j;
    	omp_set_num_threads(4);
    	for (i = 0; i < 2; i++)
    	{
    #pragma omp parallel for 
    		for (j = 0; j < 6; j++)
    		{
    			printf("i = % d,j = % d, ThreadId = % d\n", i,j, omp_get_thread_num());
    		}
    	}
    }
    

    i = 0,j = 0, ThreadId = 0
    i = 0,j = 1, ThreadId = 0
    i = 0,j = 4, ThreadId = 2
    i = 0,j = 5, ThreadId = 3
    i = 0,j = 2, ThreadId = 1
    i = 0,j = 3, ThreadId = 1
    i = 1,j = 0, ThreadId = 0
    i = 1,j = 1, ThreadId = 0
    i = 1,j = 4, ThreadId = 2
    i = 1,j = 2, ThreadId = 1
    i = 1,j = 3, ThreadId = 1
    i = 1,j = 5, ThreadId = 3

    循环并行化语句的限制:

    • 并行化的语句必须是for循环语句
    • 能够推测出循环的次数
    • 在循环过程中不能使用break语句
    • 不能使用goto和return语句从循环中跳出
    • 可以使用continue语句

    循环并行化问题

    循环迭代

    求一个数组的前若干项的值,公式如下,s[k]=s[k-2]+2*k-1

    int main()
    {
    	int i, s[1024];
    	s[0] = 0;
    	s[1] = 1;
    #pragma omp parallel for schedule(static,1) num_threads(2)
    //schedule(static,size)的含义
    //OpenMP会给每个线程分配size次迭代计算。这个分配是静态的,
    	for (i = 2; i < 1024; i++) 
    	{
    		s[i] = s[i - 2] + 2 * i - 1;
    	}
    }
    

    数据竞争问题

    一个数组求和的问题,sum=sum+a[k]

    • 并行区域内定义的局部变量及循环迭代变量都是私有变量;
    • 并行区域内的静态变量是共享变量;
    • 并行区域外定义的变量在进入并行区域是共享变量,可通过数据作用域子句消除。
    int a[1000000];
    //可自行定义
    int main()
    {
    	int sum = 0;
    #pragma omp parallel for  
    	for (long i = 1; i < 1000000; i++)
    	{
    		sum += a[i];
    	}
    }
    

    sections和section命令

    sections定义一个区域,section将此区域划分成几个不同的段,各个段由一组线程并行执行。

    格式:
    #pragma omp [parallel] sections [子句]
    {
    #pragma omp section
    { 代码块 }
    #pragma omp section
    { 代码块 }

    }

    int main()
    {
    #pragma omp parallel 
    	{
    #pragma omp sections
    	{
    #pragma omp section
    		printf("section1 thread = % d\n", omp_get_thread_num());
    #pragma omp section
    		printf("section2 thread = % d\n", omp_get_thread_num());
    	}
    #pragma omp sections
    	{
    #pragma omp section
    		printf("section3 thread = % d\n", omp_get_thread_num());
    #pragma omp section
    		printf("section4 thread = % d\n", omp_get_thread_num());
    	}
    	}
    }
    

    子句

    private (list)
    firstprivate (list)
    lastprivate (list)
    reduction (operator: list)
    nowait
    sections结构有隐式路障,nowait子句会去掉这个路障。

    	int i;
    	float a[N], b[N], c[N];
    	for (i = 0; i < N; i++)
    		a[i] = b[i] = i * 1.0;
    #pragma omp parallel shared(a,b,c) private(i) 
    	{
    #pragma omp sections nowait    
    		{
    #pragma omp section    
    			for (i = 0; i < N / 2; i++)
    				c[i] = a[i] + b[i];
    #pragma omp section    
    			for (i = N / 2; i < N; i++)
    				c[i] = a[i] + b[i];
    		}
    	}
    }
    
    

    single命令

    single指定代码块由线程组中的一个线程执行。

    single结构有隐式路障:线程组中没有执行single指令的线程会一直等待代码块的结束,使用nowait子句除外。

    int main()
    {
    	omp_set_num_threads(4);
    #pragma omp parallel 
    	{
    		cout << "test OpenMP"<<endl;
    #pragma omp single
    		{
    			cout << "test OpenMP single" << endl;
    			cout << "execute thread is " << omp_get_thread_num() << endl;
    		}
    	}
    }
    

    test OpenMP
    test OpenMP single
    execute thread is 0
    test OpenMP
    test OpenMP
    test OpenMP

    master命令

    master指定代码段由主线程执行,其它线程跨越该块。master结构无隐式路障

    int main()
    {
    	int a[5], i;
    #pragma omp parallel 
    	{
    #pragma omp for 
    		for (i = 0; i < 5; i++)
    			a[i] = i * i;
    #pragma omp master 
    		for (i = 0; i < 5; i++)
    			printf_s("a[%d] = %d\n", i, a[i]);
    #pragma omp barrier 
    #pragma omp for 
    		for (i = 0; i < 5; i++)
    			a[i] += i;
    	}
    }
    

    三、数据处理

    threadprivate命令

    threadprivate指定的全局变量被各线程复制了一份私有拷贝,即各线程的私有全局变量。

    语句格式:#pragma omp threadprivate (list)

    各线程的私有计数器

    int counter = 0;
    #pragma omp threadprivate (counter)
    void  inc_counter()
    {
    	counter++;
    }
    int main()
    {
    #pragma omp parallel
    	for (int i = 0; i < 10000; i++)
    		inc_counter();
    	printf("counter = % d\n", counter);
    }
    

    private子句

    private将一个或多个变量声明成线程私有的变量,每个线程有自己变量私有副本 。

    格式:private(list)

    在这里插入图片描述

    private声明的私有变量不能继承同名变量的值

    int main()
    {
    	int k = 100;
    #pragma omp parallel for private(k)
    	for ( k=0; k < 10; k++)
    	{     
    		printf("k=%d,thread = % d\n", k, omp_get_thread_num());
    	}
    	printf("last k=%d\n", k);
    
    }
    

    k=6,thread = 6
    k=2,thread = 2
    k=1,thread = 1
    k=5,thread = 5
    k=0,thread = 0
    k=4,thread = 4
    k=3,thread = 3
    k=7,thread = 7
    k=8,thread = 8
    k=9,thread = 9
    last k=100

    firstprivate子句

    firstprivate子句是private子句的超集

    对私有变量进行初始化,把串行变量值拷贝到私有变量中(线程开始)

    语句格式: firstprivate (list)

    int main()
    {
    	int k = 100;
    #pragma omp parallel for firstprivate(k)
    	for ( int i=0; i < 4; i++)
    	{   
    		k+=i;    
    		printf("k=%d\n",k);
    	}
    	printf("last k=%d\n", k);
    }
    

    k=101
    k=100
    k=102
    k=103
    last k=100

    lastprivate子句

    对私有变量最后的终结操作,把私有变量(最后的循环迭代或段 )拷贝到同名串行变量中

    语句格式 : lastprivate (list)

    int main()
    {
    	int val = 8;
    #pragma omp parallel for firstprivate(val) lastprivate(val)
    	for (int i = 0; i < 2; i++) {
    		printf("i = % d val = % d\n", i, val);
    		if (i == 1)
    			val = 10000;
    		printf("i = % d val = % d\n", i, val);
    	}
    	printf("val = % d\n", val);
    }
    
    

    i = 1 val = 8
    i = 1 val = 10000
    i = 0 val = 8
    i = 0 val = 8
    val = 10000

    share子句

    shared声明一个或多个变量是共享变量

    格式 shared (list)

    问题:数据竞争

    • 数据保护
    • 尽量将共享变量转化为私有变量

    default子句

    default (shared | none)

    default(shared):传入并行区域内的同名变量被当作共享变量来处理,不会产生私有副本。

    default(none):并行区域中用到的变量必须显示指定是共享变量还是私有变量。

    reduction子句

    对一个或多个变量指定一个操作符,每个线程创建变量的私有副本。

    在区域结束处,将用私有副本的值通过指定的操作符运算,运算结果更新原始变量。

    格式 : reduction (operator: list)

    在这里插入图片描述

    初始时,每个线程都保留一份私有拷贝

    在结构尾部根据指定的操作对线程中的相应变量进行规约,并更新该变量的全局值

    int main()
    {
    	int   i, n, chunk;
    	float a[100], b[100], result;
    	n = 100;
    	chunk = 10;
    	result = 0.0;
    	for (i = 0; i < n; i++)
    	{
    		a[i] = i * 1.0;
    		b[i] = i * 2.0;
    	}
    #pragma omp parallel for default(shared) private(i)\
     		schedule(static,chunk) reduction(+:result)    
    	for (i = 0; i < n; i++)
    		result = result + (a[i] * b[i]);
    	printf("Final result= %f\n", result);
    }
    

    copyin子句

    copyin:将主线程中threadprivate变量的值拷贝到并行区域的各个线程的threadprivate变量中。

    格式 : copyin(list)

    int global = 0;
    #pragma omp threadprivate(global)
    int main()
    {
    	global = 1000;
    #pragma omp parallel copyin(global)
    	{
    		printf("global = % d\n", global);
    		global = omp_get_thread_num();
    	}
    	printf("global = % d\n", global);
    	printf("parallel again\n");
    #pragma omp parallel
    	printf("global = % d\n", global);
    }
    

    copyprivate子句

    copyprivate:将single线程的变量值广播到同一并行区域的其他线程。

    语法 : copyprivate(list)

    int counter = 0;
    #pragma omp threadprivate(counter)
    int increment_counter()
    {
    	counter++;
    	return(counter);
    }
    int main()
    {
    	omp_set_num_threads(2);
    #pragma omp parallel 
    	{
    		int    count;
    #pragma omp single  copyprivate(counter)  
    		{
    			counter = 50;
    			//广播到其他线程
    		}
    		count = increment_counter();
    		cout << omp_get_thread_num() << "   "  << count<<endl;
    	}
    	return 0;
    }
    

    1 51
    0 51

    四、任务调度

    schedule子句的格式:schedule(type[,size])

    在这里插入图片描述

    静态调度

    不使用size参数

    int main()
    {
    #pragma omp parallel for schedule(static) 
    	for (int i = 0; i < 8; i++)
    	{
    		printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
    	}
    }
    

    i=0, thread_id=0
    i=2, thread_id=2
    i=1, thread_id=1
    i=3, thread_id=3
    i=6, thread_id=6
    i=7, thread_id=7
    i=4, thread_id=4
    i=5, thread_id=5

    比较散乱,每个线程分配到了迭代。

    使用size参数

    int main()
    {
    #pragma omp parallel for schedule(static,2) 
    	for (int i = 0; i < 8; i++)
    	{
    		printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
    	}
    }
    

    i=0, thread_id=0
    i=1, thread_id=0
    i=2, thread_id=1
    i=3, thread_id=1
    i=6, thread_id=3
    i=7, thread_id=3
    i=4, thread_id=2
    i=5, thread_id=2

    每个线程都分到了两次迭代

    动态调度

    不使用size参数

    int main()
    {
    #pragma omp parallel for schedule(dynamic)
    	for (int i = 0; i < 8; i++)
    	{
    		printf("i=%d, thread_id=%d\n", i, omp_get_thread_num()); 
    	}
    }
    

    i=1, thread_id=3
    i=5, thread_id=3
    i=6, thread_id=3
    i=3, thread_id=2
    i=4, thread_id=1
    i=0, thread_id=0
    i=2, thread_id=5
    i=7, thread_id=4

    每次分配一个迭代给线程

    使用size参数

    int main()
    {
    #pragma omp parallel for schedule(dynamic,2)
    	for (int i = 0; i < 8; i++)
    	{
    		printf("i=%d, thread_id=%d\n", i, omp_get_thread_num()); 
    	}
    }
    

    i=2, thread_id=0
    i=3, thread_id=0
    i=4, thread_id=2
    i=5, thread_id=2
    i=0, thread_id=1
    i=1, thread_id=1
    i=6, thread_id=11
    i=7, thread_id=11

    每个2个连续的迭代都分配了一个线程

    guided调度

    int main()
    {
    	omp_set_num_threads(3);
    	//设置线程
    #pragma omp parallel for schedule(guided,2)
    	for (int i = 0; i < 5; i++)
    	{
    		printf("i=%d, thread_id=%d\n", i, omp_get_thread_num());
    	}
    }
    

    i=0, thread_id=0
    i=1, thread_id=0
    i=2, thread_id=0
    i=3, thread_id=0
    i=8, thread_id=0
    i=9, thread_id=0
    i=6, thread_id=2
    i=7, thread_id=2
    i=4, thread_id=1
    i=5, thread_id=1

    初始分配较大的迭代次数,迭代次数按指数级下降到size次。如果没有size参数,则为1。

    五、线程同步

    互斥锁:用来保护一块共享存储空间,限制只有一个线程访问这块共享存储空间,保证了数据的完整性。

    临界区(critical),原子操作(atomic),由库函数来提供同步操作(互斥函数)

    事件同步:用来控制代码的执行顺序,使得某部分代码必须在其它代码执行完毕后才能执行。

    同步路障(barrier),顺序语句(ordered)

    临界区(critical)

    critical:表明作用域中的代码一次只能由一个线程执行,其他线程被阻塞。

    临界区代码:

    #pragma omp critical [(name)]
    block

    int main()
    {
    	int sum = 0;
    #pragma omp parallel for    
    	for (int i = 0; i < 10000; ++i)
    	{
    #pragma omp critical
    		{
    			sum = sum + i;
    		}
    	}
    	cout << sum << endl;
    }
    

    输出是确定的49995000,如果注释掉critical 则不确定。

    原子操作(atomic)

    现代体系结构的多处理机提供了原子更新内存单元的方法,提供了一种更高效率的互斥锁机制。

    atomic:指定特定的存储单元将被原子更新。

    原子操作代码:

    #pragma omp atomic
    表达式;

    int main()
    {
    	int sum = 0;
    #pragma omp parallel for    
    	for (int i = 0; i < 10000; ++i)
    	{
    		#pragma omp atomic
    		sum += i;
    	}
    	cout << sum << endl;
    }
    

    结果任然是确定的值,避免了可能出现的数据访问竞争情况。

    atomic在使用中需要注意:

    • 当对一个数据进行原子操作的时候,就不能对数据进行临界区的保护
    • 用户在针对同一个内存单元使用院子操作的时候,需要在程序所有涉及到该变量并行赋值的部位都加入原子操作的保护。

    库函数的互斥锁支持

    在这里插入图片描述

    omp_lock_t  lock;
    int counter = 0;
    void inc_counter()
    {
    	printf("thread id = % d\n", omp_get_thread_num());
    	for (int i = 0; i < 1000; i++) 
    	{
    		omp_set_lock(&lock);
    		counter++;
    		omp_unset_lock(&lock);
    	}
    }
    void dec_counter()
    {
    	printf("thread id = % d\n", omp_get_thread_num());
    	for (int i = 0; i < 1000; i++) 
    	{
    		omp_set_lock(&lock);
    		counter--;
    		omp_unset_lock(&lock);
    	}
    }
    int main()
    {
    	omp_init_lock(&lock);
    	//初始化
    #pragma omp parallel sections
    	{
    #pragma omp section
    		inc_counter();
    #pragma omp section
    		dec_counter();
    	}
    	omp_destroy_lock(&lock);
    	printf("counter = % d\n", counter);
    }
    

    thread id = 0
    thread id = 1
    counter = 0

    同步路障

    隐式的同步路障

    • #pragma omp parallel
    • #pragma omp for
    • #pragma omp single
    • #pragma omp sections

    nowait子句:可以避免不必要的同步路障。

    在这里插入图片描述

    显示的同步路障

    可以在需要的地方插入明确的同步路障语句
    #pragma omp barrier

    在并行区域的执行过程中,所有的执行线程都会在同步路障语句上进行同步

    void initialization()
    {
    	int counter = 0;
    	printf("thread %d start initialization\n", omp_get_thread_num());
    	for (int i = 0; i < 100000; i++)
    		counter++;
    	printf("thread %d finish initialization\n", omp_get_thread_num());
    }
    void process()
    {
    	int counter = 0;
    	printf("thread %d start process\n", omp_get_thread_num());
    	for (int i = 0; i < 100000; i++)
    		counter++;
    	printf("thread %d finish process\n", omp_get_thread_num());
    }
    int main()
    {
    	omp_set_num_threads(2);//2个线程
    #pragma omp parallel
    	{
    		initialization();
    #pragma omp barrier //路障 上一个函数等待执行完毕
    		process();
    	}
    	return 0;
    }
    

    thread 0 start initialization
    thread 1 start initialization
    thread 0 finish initialization
    thread 1 finish initialization
    thread 1 start process
    thread 0 start process
    thread 1 finish process
    thread 0 finish process

    顺序语句(ordered)

    ordered:指定循环区域内的代码段按循环迭代的顺序执行。

    ordered命令需要和ordered子句结合起来使用。

    格式:#pragma omp ordered

    int main()
    {
    #pragma omp parallel for ordered schedule(static, 2)
    	for (int i = 0; i < 10; i++)
    	{
    		#pragma omp ordered
    		//按照循环顺序来,不会出现顺序混乱
    			printf("i = % d\n", i);
    	}
    	return 0;
    }
    

    flush指导语句

    flush指导语句用以标识一个同步点,用以确保所有的线程看到一致的存储器视图

    语句格式 #pragma omp flush (list) newline

    flush将在下面几种情形下隐含运行,nowait子句除外

    Barrier ,critical:进入与退出部分
    ordered:进入与退出部分 ,parallel:退出部分
    for:退出部分 , sections:退出部分,single:退出部分

    六、常用库函数

    设置线程数量

    #include <omp.h>
    #ifdef _OPENMP
    	omp_set_num_threads(4);
    	//设置4个线程
    #endif
    

    获取线程ID号
    返回当前线程号,0代表主线程

    int omp_get_thread_num(void)
    

    环境变量

    在这里插入图片描述

    七、例子

    计算PI的值

    static long num_steps = 1000000;
    double step;
    #define NUM_THREADS 2 
    void main()
    {
    	double x, pi = 0.0, sum[NUM_THREADS];
    	step = 1.0 / (double)num_steps;
    	omp_set_num_threads(NUM_THREADS);
    #pragma omp parallel private(x)
    //每个线程自己私有副本  	  
    	{
    		int id = omp_get_thread_num();
    		sum[id] = 0;
    #pragma omp for
    		//以下for循环分给各个线程执行,结果保存在sum[id]里
    		for (int i = 0; i < num_steps; i++)
    		{
    			x = i * step;
    			sum[id] += 4.0 / (1.0 + x * x);
    		}
    	}
    	//各个线程的结果相加
    	for (int i = 0; i < NUM_THREADS; i++)    
    		pi += sum[i] * step;
    	cout << pi << endl;
    }
    
    展开全文
  • 第五章 Linux 多线程编程;POSIX标准;POSIX 线程库Pthreads介绍;POSIX pthreads库;POSIX pthreads库(续;POSIX pthreads库(续;POSIX pthreads库(续;...使用Pthreads编写的程序例子;使用Pthreads编写的程序例子(续;
  • 第四章 Windows多线程编程及调优;Windows多线程API;Windows线程库;使用win32线程API_beginthread;使用_beginthread创建线程的例子;线程管理;设置线程的优先级;线程的挂起与恢复;线程等待;线程终结;...
  • 本文从基础入手,主要阐述基于桌面电脑的多核程序设计的基础知识,包括一些向量化运算,虚拟机算,多线程等的相关知识总结。 一.计算平台的分类 单指令单数据流机器(SISD) 传统的串行计算机,所有指令都是串行...
  • 使用Win32线程APIWin32函数库中提供了操作多线程的函数包括创建线程管理线程终止线程线程同步等接口 线程函数DWORD WINAPI ThreadFunc (LPVOID lpvThreadParm)线程创建HANDLE CreateThread (LPSECURITY_ATTRIBUTES ...
  • 多核程序设计技术

    千次阅读 2014-04-28 18:00:23
    并发:指多个线程在某段时间内能够同时被执行。并发可以在串行处理器上通过交错执行的方法来实现。并行:指多个线程在任何时间点都同时执行。线程:一些相关指令的离散序列。 层次: 用户级/内核级/硬件线程。...
  • OpenMP多核程序设计

    2013-12-09 11:45:15
    OpenMP是一种API,用于编写可移植的多线程应用程序,无需程序员进行复杂的线程创建、同步、负载平衡和销毁工作。 使用OpenMP的好处:  1)CPU核数扩展性问题  2)方便性问题  3)可移植性问题 OpenMP指令和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,259
精华内容 503
关键字:

多核程序设计