openmp并行编程_OpenMP实现生产者消费者问题

48c77ab7ce17034084ecaf2bb4b79d7e.png

最近在学习OpenMP编程。记录下学习过程中的学习资料和历程。同时简单实现了《并行程序设计导论》中5.8节的生产者消费者问题(进程线程经典问题)。

OpenMP编程指南

参考资料:

《并行程序设计导论》第五章OpenMP

博客(知识点比书本总结全面些,里面有6篇,前4篇推荐学习):

OpenMP编程指南 - 周伟明的多核、测试专栏 - CSDN博客​blog.csdn.net
ca9051ebda56dc04dd429af2b8572095.png

超算习题(提供了虚拟练习环境,不过相对简单。有时间的推荐自己边学边敲代码实现)

在线实训​www.easyhpc.net

以上就是本人在学习OpenMP过程涉及的资料。

生产者消费者问题

理论以及基本操作学完,需要去找个问题用OpenMP实现(测试一下自己是不是真的会用了嘛!)。

本文根据《并行程序设计导论》第5章中5.8小节讲解的生产者消费者问题,用OpenMP实现了书中的伪代码。接下来开始分析这个问题。

  • 问题描述

针对典型的生产者和消费者问题,使用OpenMP编程,在共享内存系统上实现消息传递。

每一个线程有一个共享消息队列。每个线程同时充当生产者和消费者角色

生产者:线程随机产生整数“消息”和消息的目标线程,发送消息给目标线程

消费者:线程从自己的消息队列中取出消息并打印该消息

  • 问题分析

数据竞争问题:当有多个生产者向同一个消息队列写数据,存在数据竞争问题

数据竞争是主要解决的问题,同时书本还分析了很多其他问题。详细见书本!

  • 代码实现

1、数据结构

设计一个消息队列结构,生产者向队尾发送数据,消费者从队头取数据。数据结构中包含队头和队尾指针(用整数模拟,用数组模拟循环队列)。为了提高并行度,队列使用两个锁(队头锁和队尾锁)实现互斥,以解决书中提到的数据竞争的问题。

//消息队列 结构体
struct MesgQueue{
    int *mesg;
    int enqueued, dequeued;
    omp_lock_t front_mutex, back_mutex;
};

2、操作函数

初始化函数:初始化锁,消息队列的内存分配,和相关的变量

void init(MesgQueue* MQ){
    MQ->mesg = new int[send_max];
    MQ->dequeued = 0;
    MQ->enqueued = 0;
    omp_init_lock(&(MQ->front_mutex));
    omp_init_lock(&(MQ->back_mutex));
}

3、Send_msg函数

发生消息函数:向目标线程发送消息

void Send_msg(){
    int mesg = rand();
    int dest = rand() % thread_count;
    //#pragma omp critical
    omp_set_lock(&Msg[omp_get_thread_num()].back_mutex);
    Enqueue(dest, mesg);
    omp_unset_lock(&Msg[omp_get_thread_num()].back_mutex);
}

4、Try_receive函数

接收消息函数:从消息队列取消息并打印

void Try_receive(){
    int cur_p = omp_get_thread_num();
    int queue_size = Msg[cur_p].enqueued - Msg[cur_p].dequeued;
    if(queue_size == 0) return;

    else if(queue_size == 1){
        //#pragma omp critical
        Dequeue(cur_p);
    }
    else
    {
        Dequeue(cur_p);
    }
}

5、Done函数

终止函数:判断线程是否完成任务

int Done(){
    int cur_p = omp_get_thread_num();
    int queue_size = Msg[cur_p].enqueued - Msg[cur_p].dequeued;
    if(queue_size == 0 && done_sending == thread_count) return 1;
    else return 0;
}

完整代码在文尾!

  • 性能分析

书本中提到用omp critical 命令实际上,线程在发生消息依旧是串行的,因为每次只能有一个线程执行发生消息的代码。而在我们的问题中,线程0发生消息给线程1,和线程2发生消息给线程3是允许同时进行的。因此书中提到用锁机制对每个线程的消息队列进行互斥。

因此作此性能分析,观察用critical和锁时程序的不同,以及相同数据量时间上的差异。

1)critical和锁运行时(串行和并行差异)

为了更好的感受用critical和锁时线程之间的串行和并行操作,在发生消息时添加了输出

使用omp critical命令时:

73d8a0482d5dca75b6026f6eaf6fc3f8.png

可以看到,输出井然有序,线程之间先后发送消息。

使用omp_lock锁时:

1b2a937f255c11d701db61eb69832fd0.png

可以看到,输出简直不忍直视,因为当线程之间发送的目标线程不同时,可以同时执行,因此输出就很乱。而critical限制了每次只能有一个线程执行发送消息的代码。因此在实际运用openmp时要分析清楚互斥关系,准确使用好critical和锁。不然可能程序性能不但没有得到提升,甚至更差。

用书本的话总结就是:critical适用于代码块的互斥(粗粒度),锁适用于需要互斥某个数据结构(比如本文的每个线程的消息队列),(细粒度)

2)critical和锁时间差异。

顾名思义,肯定锁的时间会更少,性能会更好。

在实验过程中,小数据量(线程数和需要发送消息数都比较小时),这两个耗时相差不大。随着数据量增大,对比会比较明显(如下两个图)

omp critical:

539a2cefca37503fa2186ab209c4e995.png

锁:

864ce94a5a48c166984c3917aa969382.png

在实验中,还存在两个问题:

1) 有时候程序会无法运行出结果,如上面的critical图中第二次运行就没有结果,程序自动退出了emmm

2)当把输出都加上时(发送消息和接收消息的输出),大部分实验锁会比critical慢,不过这个应该是输出流造成的缓慢。

  • 完整代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <omp.h>
#include <time.h>

using namespace std;

int thread_count;
int send_max;
int done_sending;

struct MesgQueue{
    int *mesg;
    int enqueued, dequeued;
    omp_lock_t front_mutex, back_mutex;
};
struct MesgQueue* Msg;

void Enqueue(int dest, int mesg){
    int cur_p = omp_get_thread_num();
   // printf("Thread %d send message %d to %d success!n",cur_p, mesg, dest);
    Msg[dest].mesg[Msg[dest].enqueued] = mesg;
    Msg[dest].enqueued++; 
}

void Dequeue(int dest){
   // printf("Thread %d receive message %d success!n", dest, Msg[dest].mesg[Msg[dest].dequeued]);
    Msg[dest].dequeued++;
}

void Send_msg(){
    int mesg = rand();
    int dest = rand() % thread_count;
    //#pragma omp critical
    omp_set_lock(&Msg[omp_get_thread_num()].back_mutex);
    Enqueue(dest, mesg);
    omp_unset_lock(&Msg[omp_get_thread_num()].back_mutex);
}

void Try_receive(){
    int cur_p = omp_get_thread_num();
    int queue_size = Msg[cur_p].enqueued - Msg[cur_p].dequeued;
    if(queue_size == 0) return;

    else if(queue_size == 1){
        //#pragma omp critical
        Dequeue(cur_p);
    }
    else
    {
        Dequeue(cur_p);
    }
}

int Done(){
    int cur_p = omp_get_thread_num();
    int queue_size = Msg[cur_p].enqueued - Msg[cur_p].dequeued;
    if(queue_size == 0 && done_sending == thread_count) return 1;
    else return 0;
}

void init(MesgQueue* MQ){
    MQ->mesg = new int[send_max];
    MQ->dequeued = 0;
    MQ->enqueued = 0;
    omp_init_lock(&(MQ->front_mutex));
    omp_init_lock(&(MQ->back_mutex));
}

void destroy(){
    delete [] Msg;
}

int main(int argc, char* argv[]){

    if(argc != 2) printf("Error Command!n"), exit(0);
    thread_count = strtol(argv[1], NULL, 10);
    printf("thread_count = %d, Input the number of message:n", thread_count);
    cin>>send_max;
    
    Msg = new MesgQueue[thread_count];

    srand((unsigned)time(NULL));
    int sent_msgs, i;
    clock_t s = clock();
#pragma omp parallel num_threads(thread_count)
{   
    #pragma omp for
    for(i=0; i< thread_count; ++i) 
        init(&Msg[omp_get_thread_num()]);
    
    #pragma omp barrier

    #pragma omp for private(sent_msgs)
    for(i=0; i<thread_count; ++i){
        
        for(sent_msgs = 0; sent_msgs < send_max; ++sent_msgs){
            Send_msg();
            Try_receive();
        }
        // printf("thread %d send message done!n", omp_get_thread_num());
        #pragma omp atomic
        done_sending++;
        
        while (!Done())
        {
            Try_receive();
        }
    }
}
    destroy();
    clock_t e = clock();
    printf("Running time is: %dmsn", e-s);
    return 0;
}

欢迎交流指正!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值