嵌入式多线程应用程序设计实验.docx
- 文档编号:4710192
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:16
- 大小:549.03KB
嵌入式多线程应用程序设计实验.docx
《嵌入式多线程应用程序设计实验.docx》由会员分享,可在线阅读,更多相关《嵌入式多线程应用程序设计实验.docx(16页珍藏版)》请在冰豆网上搜索。
嵌入式多线程应用程序设计实验
2.2多线程应用程序设计
一、实验目的
¾了解多线程程序设计的基本原理。
¾学习pthread库函数的使用。
二、实验内容
读懂pthread.c的源代码,熟悉几个重要的PTHREAD库函数的使用。
掌握共享锁和信号量的使用方法。
进入/arm2410cl/exp/basic/02_pthread目录,运行make产生pthread程序,使用NFS方式连接开发主机进行运行实验。
三、预备知识
¾有C语言基础
¾掌握在Linux下常用编辑器的使用
¾掌握Makefile的编写和使用
¾掌握Linux下的程序编译与交叉编译过程
四、实验设备及工具
硬件:
UP-TECHS2410/P270DVP嵌入式实验平台,PC机Pentium500以上,硬盘40G以上,内存大于128M。
软件:
PC机操作系统REDHATLINUX9.0+MINICOM+ARM-LINUX开发环境
五、实验原理及代码分析
1.多线程程序的优缺点
多线程程序作为一种多任务、并发的工作方式,有以下的优点:
1)提高应用程序响应。
这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(timeconsuming)置于一个新的线程,可以避免这种尴尬的情况。
2)使多CPU系统更加有效。
操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3)改善程序结构。
一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
LIBC中的pthread库提供了大量的API函数,为用户编写应用程序提供支持。
2.实验源代码与结构流程图本实验为著名的生产者-消费者问题模型的实现,主程序中分别启动生产者线程和消费者线程。
生产者线程不断顺序地将0到1000的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。
流程图如图2.2.1所示:
图2.2.1生产者-消费者实验源代码结构流程图
本实验具体代码如下:
/************************************************
*Theclassicproducer-consumerexample.
*Illustratesmutexesandconditions.
*byZoujianguo
*2003-12-22
*************************************************/
#include
#include
#include
#include"pthread.h"
#defineBUFFER_SIZE16
/*设置一个整数的圆形缓冲区*/
structprodcons{
intbuffer[BUFFER_SIZE];/*缓冲区数组*/
pthread_mutex_tlock;/*互斥锁*/
intreadpos,writepos;/*读写的位置*/
pthread_cond_tnotempty;/*缓冲区非空信号*/
pthread_cond_tnotfull;/*缓冲区非满信号*/
};
/*--------------------------------------------------------*/
/*初始化缓冲区*/
voidinit(structprodcons*b)
{
pthread_mutex_init(&b->lock,NULL);pthread_cond_init(&b->notempty,NULL);pthread_cond_init(&b->notfull,NULL);
b->readpos=0;
b->writepos=0;
}
/*--------------------------------------------------------*/
/*向缓冲区中写入一个整数*/
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(&b->lock);
/*等待缓冲区非满*/
while((b->writepos+1)%BUFFER_SIZE==b->readpos){
printf("waitfornotfull\n");
pthread_cond_wait(&b->notfull,&b->lock);
}
/*写数据并且指针前移*/
b->buffer[b->writepos]=data;
b->writepos++;
if(b->writepos>=BUFFER_SIZE)b->writepos=0;
/*设置缓冲区非空信号*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/*--------------------------------------------------------*/
/*从缓冲区中读出一个整数*/
intget(structprodcons*b)
{
intdata;
pthread_mutex_lock(&b->lock);
/*等待缓冲区非空*/
while(b->writepos==b->readpos){printf("waitfornotempty\n");pthread_cond_wait(&b->notempty,&b->lock);
}
/*读数据并且指针前移*/data=b->buffer[b->readpos];b->readpos++;
if(b->readpos>=BUFFER_SIZE)b->readpos=0;
/*设置缓冲区非满信号*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
returndata;
}
/*--------------------------------------------------------*/
#defineOVER(-1)
structprodconsbuffer;
/*--------------------------------------------------------*/
void*producer(void*data)
{
intn;
for(n=0;n<1000;n++){printf("put-->%d\n",n);put(&buffer,n);
}
put(&buffer,OVER);printf("producerstopped!
\n");returnNULL;
}
/*--------------------------------------------------------*/
void*consumer(void*data)
{
intd;
while
(1){
d=get(&buffer);
if(d==OVER)break;
printf("%d-->get\n",d);
}
printf("consumerstopped!
\n");
returnNULL;
}
/*--------------------------------------------------------*/
intmain(void)
{
pthread_tth_a,th_b;void*retval;init(&buffer);
pthread_create(&th_a,NULL,producer,0);
pthread_create(&th_b,NULL,consumer,0);
/*等待生产者和消费者结束*/pthread_join(th_a,&retval);pthread_join(th_b,&retval);return0;
}
3.主要函数分析:
下面我们来看一下,生产者写入缓冲区和消费者从缓冲区读数的具体流程,生产者首先要获得互斥锁,并且判断写指针+1后是否等于读指针,如果相等则进入等待状态,等候条件变量notfull;如果不等则向缓冲区中写一个整数,并且设置条件变量为notempty,最后释放互斥锁。
消费者线程与生产者线程类似,这里就不再过多介绍了。
流程图如下:
s
图2.2.2生产消费流程图
¾生产者写入共享的循环缓冲区函数PUT
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(&b->lock);//获取互斥锁
while((b->writepos+1)%BUFFER_SIZE==b->readpos){
//如果读写位置相同
pthread_cond_wait(&b->notfull,&b->lock);
//等待状态变量b->notfull,不满则跳出阻塞
}
b->buffer[b->writepos]=data;//写入数据
b->writepos++;
if(b->writepos>=BUFFER_SIZE)b->writepos=0;
pthread_cond_signal(&b->notempty);//设置状态变量
pthread_mutex_unlock(&b->lock);//释放互斥锁
}
¾消费者读取共享的循环缓冲区函数GET
intget(structprodcons*b)
{
intdata;
pthread_mutex_lock(&b->lock);//获取互斥锁
while(b->writepos==b->readpos){//如果读写位置相同
pthread_cond_wait(&b->notempty,&b->lock);
//等待状态变量b->notempty,不空则跳出阻塞。
否则无数据可读。
}
data=b->buffer[b->readpos];//读取数据
b->readpos++;
if(b->readpos>=BUFFER_SIZE)b->readpos=0;pthread_cond_signal(&b->notfull);//设置状态变量pthread_mutex_unlock(&b->lock);//释放互斥锁
returndata;
}
4.主要的多线程API在本程序的代码中大量的使用了线程函数,如pthread_cond_signal、pthread_mutex_init、pthread_mutex_lock等等,这些函数的作用是什么,在哪里定义的,我们将在下面的内容中为大家做一个简单的介绍,并且为其中比较重要的函数做一些详细的说明。
¾线程创建函数:
intpthread_create(pthread_t*thread_id,constpthread_attr_t*attr,
void*(*start_routine)(void*),void*restrictarg)
¾获得父进程ID:
pthread_tpthread_self(void)
¾测试两个线程号是否相同:
intpthread_equal(pthread_tthread1,pthread_tthread2)
¾线程退出:
voidpthread_exit(void*retval)
¾等待指定的线程结束:
intpthread_join(pthread_tth,void**thread_return)
¾互斥量初始化:
pthread_mutex_init(pthread_mutex_t*,constpthread_mutexattr_t*)
¾销毁互斥量:
intpthread_mutex_destroy(pthread_mutex_t*
mutex)
¾再试一次获得对互斥量的锁定(非阻塞):
intpthread_mutex_trylock(pthread_mutex_t*mutex)
¾锁定互斥量(阻塞):
intpthread_mutex_lock(pthread_mutex_t*mutex)
¾解锁互斥量:
intpthread_mutex_unlock(pthread_mutex_t*mutex)
¾条件变量初始化:
intpthread_cond_init(pthread_cond_t*restrictcond,
constpthread_condattr_t*restrictcond_attr)
¾销毁条件变量COND:
intpthread_cond_destroy(pthread_cond_t*cond)
¾唤醒线程等待条件变量:
intpthread_cond_signal(pthread_cond_t*cond)
¾等待条件变量(阻塞):
intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex)
¾在指定的时间到达前等待条件变量:
intpthread_cond_timedwait(pthread_cond_t*restrictcond,
pthread_mutex_t*restrictmutex,conststructtimespec*restrictabstime)
PTHREAD库中还有大量的API函数,用户可以参考其他相关书籍。
下面我们对几个比较重要的函数做一下详细的说明:
pthread_create线程创建函数
intpthread_create(pthread_t*thread_id,constpthread_attr_t*attr,
void*(*start_routine)(void*),void*restrictarg)线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。
这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。
第二个参数我们也设为空指针,这样将生成默认属性的线程。
当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。
前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。
创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
pthread_join函数用来等待一个线程的结束。
函数原型为:
intpthread_join(pthread_tth,void**thread_return)
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储
被等待线程的返回值。
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等
待的线程结束为止,当函数返回时,被等待线程的资源被收回。
pthread_exit函数一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程
也就结束了;另一种方式是通过函数pthread_exit来实现。
它的函数原型为:
voidpthread_exit(void*retval)
唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。
最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
下面我们来介绍有关条件变量的内容。
使用互斥锁来可实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:
锁定和非锁定。
而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。
使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。
一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。
这些线程将重新锁定互斥锁并重新测试条件是否满足。
一般说来,条件变量被用来进行线线程间的同步。
pthread_cond_init函数
条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件
变量。
它的原型为:
intpthread_cond_init(pthread_cond_t*cond,constpthread_condattr_t*cond_attr)
其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。
结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。
注意初始化条件变量只有未被使用时才能重新初始化或被释放。
释放一个条件变量的函数为pthread_cond_destroy
(pthread_cond_tcond)。
pthread_cond_wait函数使线程阻塞在一个条件变量上。
它的函数原型为:
externintpthread_cond_wait(pthread_cond_t*__restrictcond,pthread_mutex_t*restrict__mutex)线程解开mutex指向的锁并被条件变量cond阻塞。
线程可以被函数pthread_cond_signal
和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的
作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的
例子中可以看到。
线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般
说来线程应该仍阻塞在这里,被等待被下一次唤醒。
这个过程一般用while语句实现。
pthread_cond_timedwait函数
另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
externintpthread_cond_timedwaitP((pthread_cond_t*cond,
pthread_mutex_t*mutex,__conststructtimespec*abstime))
它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
pthread_cond_signal函数它的函数原型为:
externintpthread_cond_signal(pthread_cond_t*cond)
它用来释放被阻塞在条件变量cond上的一个线程。
多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。
要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。
六、实验步骤
1、阅读源代及编译应用程序
进入exp/basic/02_pthread目录,使用vi编辑器或其他编辑器阅读理解源代码。
运行make
产生pthread可执行文件。
2、下载和调试
切换到minicom终端窗口,使用NFSmount开发主机的/arm2410cl到/host目录。
进入/host/exp/basic/pthread目录,运行pthread,观察运行结果的正确性。
运行程序最后一部分结果如下:
waitfornotemptyput-->994
put-->995put-->996put-->997put-->998put-->999
producerstopped!
993-->get
994-->get
995-->get
996-->get
997-->get
998-->get
999-->getconsumerstopped!
[/host/exp/basic/02_pthread]
七、思考题
1.加入一个新的线程用于处理键盘的输入,并在按键为ESC时终止所有线程。
2.线程的优先级的控制。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式 多线程 应用 程序设计 实验