2嵌入式系统设计实验二(多线程)

更新时间:2023-10-11 14:42:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

注意实验二是在实验一的基础上完成其内容,具体环境配置见实验一 目 录

实验二 多线程应用程序设计 .................................. 2

2.1实验目的 ..................................................................................................................... 2

2.2、实验内容 .................................................................................................................. 2 2.3、预备知识 .................................................................................................................. 2 2.4、实验设备及工具 ...................................................................................................... 2 2.5、实验原理及代码分析 .............................................................................................. 3 2.6、实验步骤 ................................................................................................................ 11 2.7、思考题 .................................................................................................................... 13

实验二 多线程应用程序设计

2.1实验目的

? 了解多线程程序设计的基本原理。 ? 学习pthread库函数的使用。

2.2、实验内容

? 读懂pthread.c的源代码,熟悉几个重要的pthread库函数的使用。掌握共享

锁和信号量的使用方法。

? 进入/root/share/exp/basic/02_pthread目录,运行make 产生pthread程序,

使用NFS方式连接开发主机进行运行实验。

2.3、预备知识

? 有C语言基础

? 掌握在Linux下常用编辑器的使用 ? 掌握Makefile 的编写和使用

? 掌握Linux下的程序编译与交叉编译过程

2.4、实验设备及工具

? 硬件:UP-TECH S2410/P270 DVP嵌入式实验平台,PC机Pentium 500以上, 硬

盘40G以上,内存大于128M。

? 软件:PC机操作系统REDHAT LINUX 9.0 +MINICOM + ARM-LINUX开发环境

2.5、实验原理及代码分析

1.多线程程序的优缺点

? 多线程程序作为一种多任务、并发的工作方式,有以下的优点:

? 1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很

长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

? 2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不

同的线程运行于不同的CPU上。

? 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个

独立或半独立的运行部分,这样的程序会利于理解和修改。

? Libc中的pthread库提供了大量的API函数,为用户编写应用程序提供支持。

2.实验源代码与结构流程图

? 本实验为著名的生产者-消费者问题模型的实现,主程序中分别启动生产者线

程和消费者线程。生产者线程不断顺序地将0到1000的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。流程图如图2.2.1所示:

图2.1生产者-消费者实验源代码结构流程图

? 本实验具体代码如下:

/************************************************ * The classic producer-consumer example. * Illustrates mutexes and conditions. * by Zou jian guo * 2003-12-22 *************************************************/ #include #include #include #include \#define BUFFER_SIZE 16 /* 设置一个整数的圆形缓冲区 */ struct prodcons { int buffer[BUFFER_SIZE]; /* 缓冲区数组 */ pthread_mutex_t lock; /* 互斥锁 */ int readpos, writepos; /* 读写的位置*/ pthread_cond_t notempty; /* 缓冲区非空信号 */ pthread_cond_t notfull; /*缓冲区非满信号 */ }; /*--------------------------------------------------------*/ /*初始化缓冲区*/ void init(struct prodcons * 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; } /*--------------------------------------------------------*/ /* 向缓冲区中写入一个整数*/ void put(struct prodcons * b, int data) { pthread_mutex_lock(&b->lock); /*等待缓冲区非满*/ while ((b->writepos + 1) % BUFFER_SIZE == b->readpos) { printf(\} b->buffer[b->writepos] = data; b->writepos++; pthread_cond_wait(&b->notfull, &b->lock); /*写数据并且指针前移*/ if (b->writepos >= BUFFER_SIZE) b->writepos = 0; pthread_cond_signal(&b->notempty); /*设置缓冲区非空信号*/ pthread_mutex_unlock(&b->lock); } /*--------------------------------------------------------*/ /*从缓冲区中读出一个整数 */ int get(struct prodcons * b) { } /*--------------------------------------------------------*/ #define OVER (-1) struct prodcons buffer; /*--------------------------------------------------------*/ void * producer(void * data) { int n; for (n = 0; n < 1000; n++) { pthread_mutex_unlock(&b->lock); return data; /* 等待缓冲区非空*/ while (b->writepos == b->readpos) { 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); int data; pthread_mutex_lock(&b->lock); printf(\ printf(\ put(&buffer, n); } put(&buffer, OVER); printf(\ return NULL; } /*--------------------------------------------------------*/

void * consumer(void * data) { int d; while (1) { d = get(&buffer); if (d == OVER ) break; printf(\ %d-->get\\n\ } printf(\ return NULL; } /*--------------------------------------------------------*/ int main(void) { } pthread_t th_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); return 0; /* 等待生产者和消费者结束 */ 3.主要函数分析:

? 下面我们来看一下,生产者写入缓冲区和消费者从缓冲区读数的具体流程,生

产者首先要获得互斥锁,并且判断写指针+1后是否等于读指针,如果相等则进入等待状态,等候条件变量notfull;如果不等则向缓冲区中写一个整数,并且设置条件变量为notempty,最后释放互斥锁。消费者线程与生产者线程类似,这里就不再过多介绍了。流程图如下:

s

图2.2 生产消费流程图

? 生产者写入共享的循环缓冲区函数PUT

void put(struct prodcons * b, int data) { pthread_mutex_lock(&b->lock); //获取互斥锁 while ((b->writepos + 1) % BUFFER_SIZE == b->readpos) { //如果读写位置相同 } 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); //设置状态变量 //等待状态变量b->notfull,不满则跳出阻塞 pthread_mutex_unlock(&b->lock); //释放互斥锁 ? 消费者读取共享的循环缓冲区函数GET

int get(struct prodcons * b) { int data; while (b->writepos == b->readpos) { //如果读写位置相同 pthread_mutex_lock(&b->lock); //获取互斥锁 } 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); //释放互斥锁 return data; //等待状态变量b->notempty,不空则跳出阻塞。否则无数据可读。 4.主要的多线程API

? 在本程序的代码中大量的使用了线程函数,如pthread_cond_signal、

pthread_mutex_init、pthread_mutex_lock等等,这些函数的作用是什么,在哪里定义的,我们将在下面的内容中为大家做一个简单的介绍,并且为其中比较重要的函数做一些详细的说明。

? 线程创建函数: int pthread_create (pthread_t * thread_id, __const pthread_attr_t * __attr, void *(*__start_routine) (void *),void *__restrict __arg) ? 获得父进程ID: pthread_t pthread_self (void) ? 测试两个线程号是否相同: int pthread_equal (pthread_t __thread1, pthread_t __thread2) ? 线程退出: void pthread_exit (void *__retval) ? 等待指定的线程结束: int pthread_join (pthread_t __th, void **__thread_return) ? 互斥量初始化: pthread_mutex_init (pthread_mutex_t *,__const pthread_mutexattr_t *) ? 销毁互斥量: int pthread_mutex_destroy (pthread_mutex_t *__mutex) ? 再试一次获得对互斥量的锁定(非阻塞): int pthread_mutex_trylock (pthread_mutex_t *__mutex) ? 锁定互斥量(阻塞): int pthread_mutex_lock (pthread_mutex_t *__mutex) ? 解锁互斥量: int pthread_mutex_unlock (pthread_mutex_t *__mutex) ? 条件变量初始化: int pthread_cond_init (pthread_cond_t *__restrict __cond, __const pthread_condattr_t *__restrict __cond_attr) ? 销毁条件变量COND: int pthread_cond_destroy (pthread_cond_t *__cond) ? 唤醒线程等待条件变量: int pthread_cond_signal (pthread_cond_t *__cond) ? 等待条件变量(阻塞): int pthread_cond_wait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex) ? 在指定的时间到达前等待条件变量: int pthread_cond_timedwait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex, __const struct timespec *__restrict __abstime) ? PTHREAD库中还有大量的API函数,用户可以参考其他相关书籍。下面我们对

几个比较重要的函数做一下详细的说明: ? pthread_create线程创建函数

int pthread_create (pthread_t * thread_id,__const pthread_attr_t * __attr, void *(*__start_routine) (void *),void *__restrict __arg) ? 线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程

属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

pthread_join函数 用来等待一个线程的结束。函数原型为: int pthread_join (pthread_t __th, void **__thread_return) ? 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可

以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。

pthread_exit函数

? 一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调

用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

void pthread_exit (void *__retval) ? 唯一的参数是函数的返回代码,只要pthread_join中的第二个参数

thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

? 下面我们来介绍有关条件变量的内容。使用互斥锁来可实现线程间数据的共享

和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线线程间的同步。

pthread_cond_init函数

? 条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始

化一个条件变量。它的原型为:

int pthread_cond_init (pthread_cond_t * cond, __const pthread_condattr_t * cond_attr) ? 其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结

构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。

pthread_cond_wait函数 使线程阻塞在一个条件变量上。它的函数原型为: extern int pthread_cond_wait (pthread_cond_t *__restrict__cond, pthread_mutex_t *__restrict __mutex) 线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数

pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。

pthread_cond_timedwait函数

? 另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:

extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond, pthread_mutex_t *__mutex, __const struct timespec *__abstime)) ? 它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,

即使条件变量不满足,阻塞也被解除。

pthread_cond_signal函数 它的函数原型为: extern int pthread_cond_signal (pthread_cond_t *__cond) ? 它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变

量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。

2.6、实验步骤

1、阅读源代及编译应用程序

? 进入/root/share/exp/basic/02_pthread目录,使用vi编辑器或其他编辑器

阅读理解源代码。运行make产生pthread可执行文件。

图2.3在linux下make多线程

2、下切换到minicom终端窗口,先像实验一一样,把串口、网线、电源线接好,配置好

实验箱的IP地址,然后使用NFS mount宿主机(虚拟linux)的/root/share 到目标板(实

验箱)/host目录。具体命令见图片中的命令,注意IP地址根据自己的情况进行相应的修

改:

图2.4配置实验箱IP地址并mount 宿主机linux的/root/share到目标板(实验箱)/host

目录

? 进入/host/exp/basic/pthread目录,运行pthread,观察运行结果的正确性。

运行程序最后一部分结果如下:

?

图2.5进入/host/exp/basic/pthread目录 运行pthread

?

图2.6运行结果画面

2.7、思考题

? 1.加入一个新的线程用于处理键盘的输入,并在按键为ESC时终止所有线程。 ? 2.线程的优先级的控制。

本文来源:https://www.bwwdw.com/article/5lnf.html

Top