进程管理
第3章 进程
3.5 下面设计的优点和缺点分别是什么? 分别从操作系统层面和用户层面来阐述。
• 同步和异步通信
• 自动和显式缓冲
• 复制传送和引用传送
• 固定大小和可变大小消息
答:
同步和异步通信: 同步,对于发送进程阻塞,直到消息被接收进程或邮箱所接收;对于接收进程阻塞,直到有消息可用。而异步通信不存在阻塞问题。使用同步通信设计者则无需关心生产者—消费者问题。相比较使用异步通信,可以提高通信系统的效率。
自动或显式缓冲: 自动缓冲提供了一个无限长度的队列,从而保证了发送者在发送消息时不会遇到阻塞。如何提供自动缓存机制因系统而异。当保证有足够大内存的时候,也会有许多内存被浪费。而在缓存大小明确的情况下,发送者仅当临时队列溢出的时候才会被阻塞。此策略下内存很少会被浪费。复制发送和引用发送: 复制发送不允许接收者改变参数的状态,引用发送是允许的。引用发送允许的优点之一是它允许程序员写一个分布式版本的一个集中的应用程序。如,Java’s RMI公司提供两种发送,但引用传递一个参数需要声明这个参数是一个远程对象。
固定大小和可变大小消息: 如果只能发送定长消息,那么系统级的实现十分简单,但是却使编程任务变得更加困难;相反的是,可变大小消息要求更复杂的系统级实现,但是编程任务变得较为简单。
3.6 Fibonacci 序列是一组数: 0 , 1, 1, 2, 3, 5, 8,…
使用系统调用 fork() 编写一个 C 程序,使其在子程序中生成 Fibonacci 序列,序列的号码将在命令行中提供。例如,如果提供的是 5,Fibonacci 序列中的前 5 个数将由子进程输出。退出程序前,父进程调用 wait() 调用来等待子进程结束。执行必要的错误检查以保证不会接受命令行传递来的负数号码。
答:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
pid_t pid;
int input;
scanf("%d", &input);
int f0,f1,f2;
f0=0;
f1=1;
pid=fork();
if(pid<0){
//error fork
printf("error fork!!!\n");
exit(-1);
}
else if(pid == 0)
{
//children process
printf("Children process begin!\n");
printf("the number We input is %d\n",input);
printf("0 1 ");
for(int i=2;i<input;i++){
f2=f0+f1;
f0=f1;
f1=f2;
printf("%d ",f2);
}
printf("\n");
printf("Children process end!\n");
}
else{
//parent process
wait(NULL);
printf("Parent process end\n");
}
return 0;
}
Ubuntu vscode运行结果:input = 5
第4章 线程
4.7 如图所示程序使用了Pthread API,程序中line C和line P将会输出什么呢?
#include <pthread.h>
#include <stdio.h>
int value = 0;
void* runner(void* param); // the thread
int main(int argc,char* argv[]){
int pid;
pthread_t tid;
pthread_attr_t attr;
pid = fork();
if(pid==0){
//子进程创建线程
pthread_attr_init(&attr);
pthread_create(&tid, &attr, runner, NULL);
pthread_join(tid,NULL);
printf("CHILD: value = %d, &value = %p\n", value,&value);//line C
}else if(pid > 0){
//父进程等待子进程执行结束
wait(NULL);
printf("PARENT: value = %d, &value = %p\n", value,&value);//line P
}
return 0;
}
void* runner(void* param){
value = 5;
pthread_exit(0);
}
注意:pthread.h 不是 Linux 系统的默认库,因此需要在编译时候加入 -l pthread 。
由结果可知,子进程中的value改变而父进程的value未受影响。这可以说明,全局变量在子进程与父进程中并不是共享的。而在子进程中,value是在新创建的子线程中被修改的,这说明父线程与子线程间是共享全局变量的。
扩展一下,在父子进程中都打印value的地址,显示的结果竟然是一样的。原因在于所打印变量的地址都是逻辑地址。 对于父子进程,它们的逻辑空间一样(fork),但是物理空间却是不同的。所以在多进程编程中,不要寄希望于通过地址来判断两个变量是否相同。
4.11 Fibonacci 序列为 0,1,1,2,3,5,… 使用 Java,Pthread或Win32 线程库编写一个多线程程序来生成 Fibonacci 序列。程序应这样工作: 用户运行程序时在命令行输入要产生Fibonacci序列的数,然后程序创建一个新的线程来产生 Fibonacci 数,把这个序列放到线程共享的数据中(数组可能是一种最方便的数据结构)。当线程执行完成后,父线程将输出由子线程产生的序列。由于在子线程结束前,父线程不能开始输出 Fibonacci 序列,因此父线程必须等待子线程的结束。
答:采用java多线程(最好是用线程池,但是这里我直接创建线程)
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
if (args.length == 1) {
int n = Integer.parseInt(args[0]);
List<Integer> list = new ArrayList<>();
//通过list对象的引用来共享数据
Thread thread = new Thread(new Fibonacci(n,list));
thread.start();
try{
//父线程等待子线程结束
thread.join();
System.out.println("父线程ID: "+ Thread.currentThread().getId());
System.out.println(list);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("error args!");
}
}
}
class Fibonacci implements Runnable {
int n = 0;
List<Integer> list = null;
public Fibonacci(int n, List<Integer> list) {
this.n = n;
this.list = list;
}
@Override
public void run() {
//run方法以线程方式执行
int first = 0;
int second = 1;
if (n >= 1) {
list.add(first);
if (n >= 2) {
list.add(second);
}
} else {
return;
}
for (int i = 2; i < n; i++) {
int temp = first + second;
first = second;
second = temp;
list.add(second);
}
System.out.println("子线程ID: "+ Thread.currentThread().getId());
}
}
Win10 IDEA-2021.2 执行结果:命令行参数为10
第5章 CPU调度
第6章 进程同步
6.11 理发师问题
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) throws InterruptedException {
SleepingBarber sb = new SleepingBarber();
Thread barber = new Thread(new Barber(sb));
barber.start();
int size = 20;
Thread[] customers = new Thread[size];
for (int i = 0; i < size; i++) {
customers[i] = new Thread(new Customer(sb));
}
for (int i = 0; i < size; i++) {
customers[i].start();
Thread.sleep(1000);
}
}
}
class SleepingBarber {
/**
* 对于理发师来说的资源数 用于阻塞理发师 阻塞理发师 就意味着理发师睡觉
*/
private Semaphore customer;
/**
* 对于顾客来说的资源数 用于阻塞顾客
*/
private Semaphore barber;
private Semaphore mutex;
private int waiting = 0;
public SleepingBarber() {
waiting = 0;
mutex = new Semaphore(1);
customer = new Semaphore(0);
barber = new Semaphore(1);
}
public void come() throws InterruptedException {
mutex.acquire();
if (waiting < 3) {
waiting++;
} else {
System.out.println("人满了, 所以顾客来了又走了");
mutex.release();
return;
}
mutex.release();
customer.release();
System.out.println("一位顾客进店等待");
barber.acquire();
}
public void haiCut() throws InterruptedException {
customer.acquire();
mutex.acquire();
waiting--;
System.out.println("一位顾客理完发走了");
mutex.release();
barber.release();
}
}
class Customer implements Runnable {
private SleepingBarber sleepingBarber;
public Customer(SleepingBarber sleepingBarber) {
this.sleepingBarber = sleepingBarber;
}
@Override
public void run() {
try {
sleepingBarber.come();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Barber implements Runnable {
private SleepingBarber sleepingBarber;
public Barber(SleepingBarber sleepingBarber) {
this.sleepingBarber = sleepingBarber;
}
@Override
public void run() {
while (true) {
try {
//理发一次需要2s
Thread.sleep(2000);
sleepingBarber.haiCut();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
IDEA运行结果:
一位顾客进店等待
一位顾客进店等待
一位顾客理完发走了
一位顾客进店等待
一位顾客进店等待
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客进店等待
人满了, 所以顾客来了又走了
一位顾客理完发走了
一位顾客理完发走了
一位顾客理完发走了
(之后由于没有顾客前来,所以理发师线程阻塞,一直在睡觉….)