资源描述
开放性实验报告
题 目: 基于ARM的多线程应用程序设计
院系名称: 电气工程学院
专业班级: 自动1302
学生姓名:
学 号:
指导教师:
成绩:
指导老师签名:
日期:2017.1.6
目 录
1 系统概况 1
2 完成步骤 1
2.1 思路分析 1
2.2 结构流程图 2
2.3 重要函数 3
2.3.1源程序 3
2.3.2函数分析 9
3 实验数据 12
3.1下载和调试截图 12
4 结果分析和总结 12
设计心得 15
参考文献 16
1 系统概况
生产者-消费者问题是一个经典的线程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。在同一个线程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。
多个生产/消费者在有界缓冲上操作。它利用N个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品。当缓冲区空时消费者应阻塞睡眠,而当缓冲区满时生产者应当阻塞睡眠。一旦缓冲区中有空单元,生产者线程就向空单元中入写字符,并报告写的内容和位置。一旦缓冲区中有未读过的字符,消费者线程就从该单元中读出字符,并报告读取位置。生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符。
2 完成步骤
2.1 思路分析
本试验是练习生产者-消费者问题,成性能分析,使理解掌握线程的同步、通信以及互斥和多线程的安全问题。
一般情况下,解决互斥方法常用信号量和互斥锁,即semaphore和mutex,而解决这个问题,多采用一个类似资源槽的结构,每个槽位标示了指向资源的指针以及该槽位的状态,生产者和消费者互斥查询资源槽,判断是否有产品或者有空位可以生产,然后进行相应的操作。同时,为了告诉生产者或者消费者资源槽的情况,还要有一个消息传送机制,无论是管道还是线程通信。
为了保证互斥要求,需要定义一个数据结构,这个数据结构包含两个指针,一个读一个写,同时有一个资源数目量,告诉生产者和消费者是否可以生产或者消费。由于该数据结构很小,因而可以对此结构互斥访问。同时,对于每组数据,都有一个标志位,表示此组数据是否被占用,生产者和消费者均可以先占用此位置然后完成相应的操作。
当消费者互斥访问此结构时,首先判断是否有数据可以取,如果没有,直接等待,若有数据可取,先更改标志位占用此数据,并将资源数目-1。然后交出互斥,把数据拷贝到自己缓冲区内,清空数据。当生产者访问时,首先判断有没有空位可以生产,如果没有,直接等待,若有数据可以生产,先判断该位是否被占用,如果没被占用,则占用此位置进行生产。生产完成后,将占用位改为未占用,同时将资源数目+1。
2.2 结构流程图
试验为生产者-消费者问题模型的实现,主程序中分别启动生产者线程和消费者线程。生产者线程不断顺序地将0到9的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。试验流程图如下:
图2-1 结构流程图
2.3 重要函数
2.3.1源程序
试验代码:
/************main.c************/
#include "producer.h"
#include "customer.h"
#include "data.h"
#include <pthread.h>
#include <stdio.h>
/*******************************************************************
**实现生产者线程生产数据放入一个单向链表中,消费者线程负责消费数据**
********************************************************************/
int main(){
pthread_t thread_pro;
pthread_t thread_cons;
printf("create....\n");
//创建生产者线程。
pthread_create(&thread_pro,NULL,(void *)producer,NULL);
//创建消费者线程。
pthread_create(&thread_cons,NULL,(void *)customer,NULL);
printf("finished!\n");
while(1){
}
return 0;
}
/************data.h***********/
#ifndef DATA_H_
#define DATA_H_
//单向链表数据结构
struct product{
struct product *pre_product;
char product_data[20];
struct product *next_product;
};
//向单向链表中加入一个节点(生产)。
void addProduct(char *product_data);
//从单向链表中取出全部节点信息(消费)。
void consProduct();
#endif
/***********data.c************/
#include "data.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct product *present_product=NULL;
struct product *pre_product = NULL;
int lock=0;
void addProduct(char *product_data){
while(lock==1);
lock=1;
struct product *new_product=malloc(sizeof(struct product));
if(present_product==NULL){
new_product->pre_product=NULL;
strcpy( new_product->product_data,product_data);
new_product->next_product=NULL;
present_product=new_product;
}else{
new_product->pre_product=present_product;
strcpy( new_product->product_data,product_data);
new_product->next_product=NULL;
present_product->next_product=new_product;
present_product=new_product;
}
lock=0;
}
void consProduct(){
while(lock==1);
lock=1;
while(present_product!=NULL){
pre_product=present_product->pre_product;
printf("%s\n",present_product->product_data);
if(pre_product!=NULL){
pre_product->next_product=0;
}
free(present_product);
present_product=pre_product;
}
lock=0;
}
/*************producer.h*************/
#ifndef PRODUCER_H_
#define PRODUCER_H_
//生产者执行生产任务
void producer();
#endif
/*************producer.c**************/
#include "producer.h"
#include "data.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int temp_i=0;
void producer(){
char temp[20]={0};
while(1){
sprintf(temp,"number___%d",temp_i);
addProduct(temp);
temp_i++;
usleep((int)(rand()/2000));
}
}
/************customer.h***********/
#ifndef CUSTOMER_H_
#define CUSTOMER_H_
//消费者执行消费任务。
void customer();
#endif
/*************customer.c*************/
#include "customer.h"
#include "data.h"
#include <stdlib.h>
#include <stdio.h>
void customer(){
while(1){
consProduct();
printf("-------------\n");
usleep((int)(rand()/1000));
}
}
int main(int argc, char **argv)
{
pthread_t producer_id;
pthread_t consumer_id;
pthread_create(&producer_id, NULL, producer, NULL);
void *producer(void *arg) {
pthread_detach(pthread_self());
while(1)
{
pthread_mutex_lock(&mutex);
if (size == MALE_LENGTH){
printf("person:buff is full(producer)\n");
producer_wait = 1;
pthread_cond_wait(&full_cond, &mutex);
producer_wait = 0;
}
rear = (rear + 1) % MALE_LENGTH;
buff[rear] = rand() % MALE_LENGTH;
printf("producer: %d: %d\n", rear, buff[rear]);
++size;
if (size == 1){
while (1){
if (consumer_wait){
pthread_cond_signal(&empty_cond);
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
}
pthread_create(&consumer_id, NULL, consumer, NULL);
void *consumer(void *arg) {
pthread_detach(pthread_self());
while(1){
pthread_mutex_lock(&mutex);
if(size == 0){
printf("person:buff is empty(consumer)\n");
consumer_wait = 1;
pthread_cond_wait(&empty_cond, &mutex);
consumer_wait = 0;
}
printf("consumer:%d: %d\n", front, buff[front]);
front = (front + 1) % MALE_LENGTH;
--size;
if (size == MALE_LENGTH-1){
while(1){
if(producer_wait){
pthread_cond_signal(&full_cond);
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
}
sleep(1);
return 0;
}
2.3.2函数分析
生产者写入缓冲区和消费者从缓冲区读数的具体流程,生产者首先要获得互斥锁,并且判断资源槽是否已满,如果资源槽已满则进入等待状态,等待信号 full ;如果不满则向资源槽中写一个整数,并且释放信号为 empty,最后释放互斥锁。消费者线程与生产者线程类似,流程图如下:
图2-2 生产消费流程图
1)生产者写入共享的循环缓冲区入口函数
首先我们需要线程分离,调用pthread_detach()即可,线程分离是不让其他线程等待,继续运行。在主循环中我们需要给资源槽上锁,不让消费者函数访问,然后判断资源槽是否满,若为满就解锁资源槽,然后等待消费者激活信号full —_cond到来。若没满就增加一个数值给资源槽,然后判断资源槽是否有数据,若有就激活消费者信号empty_cond来消费。
void *producer(void *arg) {
pthread_detach(pthread_self());
while(1){
pthread_mutex_lock(&mutex);
if (size == MALE_LENGTH)
{
printf("person:buff is full(producer)\n");
producer_wait = 1;
pthread_cond_wait(&full_cond, &mutex);
producer_wait = 0;
}
rear = (rear + 1) % MALE_LENGTH;
buff[rear] = rand() % MALE_LENGTH;
printf("producer: %d: %d\n", rear, buff[rear]);
++size;
if (size == 1){
while (1){
if (consumer_wait){
pthread_cond_signal(&empty_cond);
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
}
2)消费者读取共享的循环缓冲区函数 GET
首先需要线程分离,调用pthread_detach()即可。在主循环中我们需要给资源槽上锁,不让生产者函数访问,然后判断资源槽是否满,若不满就解锁资源槽,然后等待消生产者激活信号empty _cond到来。若满就读取一个数值,然后判断资源槽是否有数据,若无就激活生产者信号full_cond来生产。
void *consumer(void *arg) {
pthread_detach(pthread_self());
while(1){
pthread_mutex_lock(&mutex);
if(size == 0){
printf("person:buff is empty(consumer)\n");
consumer_wait = 1;
pthread_cond_wait(&empty_cond, &mutex);
consumer_wait = 0;
}
printf("consumer:%d: %d\n", front, buff[front]);
front = (front + 1) % MALE_LENGTH;
--size;
if (size == MALE_LENGTH-1){
while(1){
if(producer_wait){
pthread_cond_signal(&full_cond);
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
}
3 实验数据
3.1下载和调试截图
首先进入目录/mnt/hgfs/share/JXK,然后输入命令gcc xiankun-j.c -o xiankun-j.exe -lpthread编译代码,之后运行可执行文件./xiankun-j.exe,运行结果如图3.1所示。
图3.1
4 结果分析和总结
1、具体应用情况
生产者-消费者是一个很广泛的问题,代表了多线程互斥访问共享资源,以及线程之间通信的各种问题的总和。这个可以从数据看出,简单有无等待的设置,可以让数据差异变的如此明显。毕竟,在实际应用中,生产者如何生产数据,产生怎样的数据,而消费者又如何拿到数据,是通过管道还是内存共享区,拿到数据之后,是进行处理还是写入磁盘,都有可能,因而,简单通过几组数据来判断哪个进程的效率更高,是不科学的。
我们可以通过两个实际的例子来解释有无等待的情况。有等待更像是在火锅店吃火锅,商家只要把锅准备好,将生菜切好端上来即可,而顾客大量的时间用在食用上,等待的时间也很少,这样资源位置会大量被消耗,但是实际资源消耗的速度很慢,但是由于空间被占用,生产者无法继续生产。而无等待更像是快餐外卖,商家花大量时间将食物准备好,而消费者只需要交钱拿东西走人即可,资源位置空闲很多,消费者等待时间较长,但是处理数据的过程简单又节约资源。
这里还有一个问题,就是资源槽的数量,理论上这个方案应该放弃资源槽,但是从实际实现方法来看,只不过将每个资源槽的标志位移动到资源内部,然后通过读写指针进行查询,实际上这还是资源槽的变相实现方式。但是单就资源槽的数目来看,是个很复杂的问题,要保证在生产者想生产的时候不会没槽位,而消费者查询资源槽状态又不会花费太长时间,这需要具体分析每个函数执行的时间,因而本实验中只是简单设计为消费者数量的1-3倍。
2、线程的调度情况
首先要考虑的是线程的调度顺序。为了方便起见,每个线程在产生的时候都有一个编号,而创建也是按照1-Count这样的顺序for循环创建的。如果线程调度的顺序和线程创建的顺序一样,那么在无等待情况下应该有这样的结果:消费者远多于生产者,这样当生产者生产出数据时,前面几个优先完成等待并读取数据的进程就可以占据先机,拿到数据(这里不考虑数据消费的时间,因为程序的思路和食堂占座差不多,只要把位置占掉就可以了),而后面一些进程则由于位置不佳基本拿不到数据。
当然,插入等待时间也是一种解决资源分配的方式,这样可以保证每个线程都有机会获取到资源,不至于让某个线程连续的占用资源很长时间。但是在实际情况下,每个线程完成工作不一样,又不可能像实验写出的进程那样无私,拿到资源后就自动等待,这就需要使用线程间通信机制了,让拿不到资源的线程挂起,生产者完成生产后再唤醒这些进程。
3、可能的改进
首先,就是线程调度的方式,忙等肯定不是一个好方法,如果换成线程间通信,轮流挂起或者唤醒某个线程,可能会更好。
其次,是消费者的资源分配问题。程序设计的消费者有一个1024字节的buffer,用来处理接收到的数据,但是这个buffer直到线程消失才被回收,如果加上一些资源回收的机制可能会改善程序的性能。
最后,是对于生产者和消费者具体完成工作的问题,应该说这个实验设计的场景还是很简单,完全的内存拷贝,没有什么复杂运算和文件操作,而且由于手头没有原始版本的代码,无法比较两个代码的性能,整个程序的框架也是重新编写的,这应该也是一点不足。
通过这次试验的练习,我对线程、进程等知识有了更直观深入的了解,同时也深感开发程序的不易,通过写这几个程序,发现最常见的运行错误莫过于“段错误”,这个错误的范围很广,从访问越界,到数组上下限错误,再到内存或者堆栈溢出,都可能触发这个错误,而如果没有一定的经验这是相当棘手的问题。当然,这个综合练习实验有很好的价值,除了涉及多个知识点之外,自己在做程序的时候也通过解决问题,对一些函数认识的更加透彻,例如对进程间的通信,多线程的安全问题,管道问题都有了进一步的了解。
设计心得
此次开发性试验设计让我收获甚多。一是要有一个积极的心态,独立解决问题的意识,培养扎实基础的认识。不要什么东西都感觉跟简单(很多东西可能是看似简单)就不去做了或者不屑一做,以至于性网上搜搜就可以了,这样很不好。有自己的东西有自己的付出才会有程序运行成功时的喜悦和小自豪,这样也有助于培养自己的兴趣。要时刻牢记态度决定一切。其次是兴趣,感觉学习工作中兴趣很关键,只是一个引发人积极性的问题,有了兴趣就自觉了,效率自然就高了。再次要敢于尝试和挑战。不要安于现成的程序,而且不要害怕失败,在程序调试的过程中这点尤为重要,“发现出问题然后解决问题”是一个积累经验的过程,而且很高效。最后要不懈追求。对于源代码进行不断的完善,要尽可能的实现课题所要求的功能。对于初学者或者开发较少的人来说,大量大写程序还是有必要的,但同时要注意思考,理解其实现的内在意义。还可以自己添加一些有意义的功能来实现。当看到自己编写的程序正常运行时,兴趣也会随之而来,乐此不疲,形成一个良性循环。
短短一周的开放性ARM多线程设计很快结束了,我发现我对嵌入式这个方向、对嵌入式技术、对Linux都有了新的认识。通过这次的编程,我了解到,要真真正正的掌握计算机程序还不是一件简单容易的事儿,但真正掌握后,它带给我们的将是无穷的便捷与科技,我喜欢高端便捷的生活。我希望我能做计算机这个万能机器人的主人而不是奴隶,我会努力加油的!
参考文献
[1]徐千洋.Linux C函数库参考手册.[M]中国青年出版社.2002
[2]马忠梅,马广云,徐英慧,田译.ARM嵌入式处理结构与应用基础[M].北京航空航天大学出版社.2002
[3]邹思铁.嵌入式Linux设计与应用[M].北京清华大学出版社.2002
[4]杜春雷.ARM体系结构与编程[M].清华大学出版社.2003
[5]田泽.嵌入式系统开发与应用[M].北京航空航天大学出版社.2005 [11]陈鑫.嵌入式软件技术的现状与发展动向[M].软件世界.2001
[6]田泽.嵌入式系统开发与应用实验教程[M].北京航空航天大学出版社.2004
[7]Alessandro Rubini,Jonathan Corbet.Linux设备驱动程序[M].中国电力出版社.2002
16
展开阅读全文