实验二、嵌入式Linux多线程编程实验
一、实验目的
1. 熟悉线程的定义、创建及应用方法,掌握编译源代码时引入线程库的方法。
2. 掌握如何利用信号量完成线程间的同步与互斥。
3. 熟悉Makefile工作原理,掌握编写Makefile的编写方法。
二、实验基本要求
1. 掌握熟悉线程的定义及操作方法。
2. 利用信号量的PV操作完成完成以下单个生产者和单个消费者模型的代码。
3. 编写在Ubuntu中编译执行的makefile文件,然后在Ubuntu中执行。
4. 编写在实验箱中编译执行的makefile文件,然后在实验箱中执行。注意Makefile编写规范缩进应使用制表键即Tab键。
三、实验原理
1.Linux线程的定义
线程(thread)是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述和信号处理。在两个普通进程(非线程)间进行切换时,内核准备从一个进程的上下文切换到另一个进程的上下文要花费很大的开销。这里上下文切换的主要任务是保存老进程CPU状态并加载新进程的保存状态,用新进程的内存映像替换进程的内存映像。线程允许你的进程在几个正在运行的任务之间进行切换,而不必执行前面提到的完整的上下文。另外本文介绍的线程是针对POSIX线程,也就是pthread。也因为Linux对它的支持最好。相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。也可以将线程和轻量级进程(LWP)视为等同的,但其实在不同的系统/实现中有不同的解释,LWP更恰当的解释为一个虚拟CPU或内核的线程。它可以帮助用户态线程实现一些特殊的功能。Pthread是一种标准化模型,它用来把一个程序分成一组能够同时执行的任务。
2. 什么场合会使用Pthread即线程
(1) 在返回前阻塞的I/O任务能够使用一个线程处理I/O,同时继续执行其他处理任务。
(2) 在有一个或多个任务受不确定性事件,比如网络通信的可获得性影响的场合,能够使用线程处理这些异步事件,同时继续执行正常的处理。
(3) 如果某些程序功能比其他的功能更重要,可以使用线程以保证所有功能都出现,但那些时间密集型的功能具有更高的优先级。
以上三点可以归纳为:在检查程序中潜在的并行性时,也就是说在要找出能够同时执行任务时使用Pthread。上面已经介绍了,Linux进程模型提供了执行多个进程的能力,已经可以进行并行或并发编程,可是线程能够让你对多个任务的控制程度更好、使用资源更少,因为一个单一的资源,如全局变量,可以由多个线程共享。而且,在拥有多个处理器的系统上,多线程应用会比用多个进程实现的应用执行速度更快。
3. 信号量
信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的初值不能为负,且其值只能由P、V操作来改变。
4. 信号量的PV操作
P、V操作由P操作原语和V操作原语组成(原语是不可中断的过程),对信号量S进行操作,具体定义如下:
P(S):
① 将信号量S的值减1,即S=S-1;
② 如果S≥0,则该进程继续执行;否则该进程状态置为阻塞状态,进程PCB排入信号量PCB队列末尾,放弃CPU,等待V操作的执行。
V(S):
- 将信号量S的值加1,即S=S+1;
- 如果S≤0,释放信号量队列中第一个PCB所对应的进程,将进程状态由阻塞态改为就绪态。执行V操作的进程继续执行。
一般来说,信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S<=0,表示有某些进程正在等待该资源,因此要唤醒一个阻塞状态的进程,使之成为就绪状态。
利用信号量PV操作实现线程互斥的模型:设初始化信号量S=1;且A和B共用同一临界区。
线程A 线程B
P(S); P(S);
临界区; 临界区;
V(S); V(S);
利用信号量PV操作实现线程同步的模型:设初始化信号量S=0;且A执行完才能执行B.
线程A 线程B
执行A; P(S)
V(S); 执行B;
利用信号量PV操作实现线程交替同步的模型:设初始化同步信号量S1=1,同步信号量S2=0. 执行A,执行B,再执行A,执行B……,交替执行。
线程A 线程B
P(S1); P(S2);
执行A; 执行B;
V(S2); V(S1);
四、实验内容
1. 通过网络查找资料学习线程的定义、线程的创建、线程退出、线程阻塞。
2. 通过网络查找资料学习信号量的定义、信号量的初始化、信号量P操作、信号量V操作。
3. 掌握单个生产者与单个消费者之间共用N个缓冲区的同步问题。
“单个生产者和单个消费者”问题描述如下:有N个缓冲区,生产者与消费者分别不停地把产品放入缓冲区和从缓冲区中拿走产品。生产者在缓冲区满时,它必须等待;消费者在缓冲区为空时,也必须等待。另外,因为缓冲区是临界资源,所以生产者和消费者之间必须交替同步执行。他们之间的关系如图所示。
1 | 2 | 3 | …. | N-1 | N |
生产者 |
消费者 |
生产者 |
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSIZE 10
int stack[MAXSIZE];
int size=50;
int front=-1,rear=0;
sem_t avail,full; //avail表示可用的空缓冲区,full表示已存放产品的缓冲区
//生产者
void provider_fun(void)
{
int i=1;
sleep(5);
while(i<=size) //生产50个产品,需要放入到MAXSIZE个缓冲区中
{
___________________; //avail信号量P操作,表示将可用的空缓冲区个数减1
stack[rear]=i;
printf("produce the %d product\n",stack[rear]);
rear=(rear+1)%MAXSIZE;
i++;
sleep(1);
____________________; //full信号量V操作,表示将存放产品的缓冲区个数加1
}
pthread_exit(NULL);
}
//消费者
void customer_fun(void)
{
int i=1;
while(i<=size)
{
_______________________; //fulll信号量P操作,表示将存放产品的缓冲区个数减1
front=(front+1)%MAXSIZE;
printf("\t consume the %d product\n",stack[front]);
stack[front]=0;
sleep(2);
_______________________; //avail信号量V操作,表示将可用的空缓冲区个数加1
i++;
}
pthread_exit(NULL);
}
void main()
{
pthread_t provider,customer; //定义生产者线程对象和消费者线程对象
__________________________; //将avail信号量初始化为MAXSIZE
__________________________; //将full信号量初始化为0
_______________________________________________________;//创建生产者线程
________________________________________________________;//消费者线程
pthread_join(provider,NULL);
pthread_join(customer,NULL);
sem_destroy(&avail);
sem_destroy(&full);
}
实验结果及分析:
1. 分析以上程序的工作原理。
2. 写出在实验箱编译执行的makefile文件。
3. 写出退出程序的线程源代码。