生产者消费者伪码_OpenMP实现生产者消费者问题

本文介绍了如何使用OpenMP解决生产者消费者问题,详细分析了数据竞争问题,并提供了使用OpenMP锁机制的代码实现。通过性能分析,比较了`omp critical`与锁在并发性能上的差异。
摘要由CSDN通过智能技术生成

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

OpenMP编程指南

参考资料:

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

博客(知识点比书本总结全面些,里面有6篇,前4篇推荐学习):OpenMP编程指南 - 周伟明的多核、测试专栏 - CSDN博客​blog.csdn.net

超算习题(提供了虚拟练习环境,不过相对简单。有时间的推荐自己边学边敲代码实现)在线实训​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命令时:

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

使用omp_lock锁时:

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

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

2)critical和锁时间差异。

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

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

omp critical:

锁:

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

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

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

#include #include #include #include #include #include

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

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: %dms\n", e-s);

return 0;

}

欢迎交流指正!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值