*** 作系统 实验二 进程线程基本编程练习

 *** 作系统 实验二 进程线程基本编程练习,第1张

【要求】所有练习题保留题目要求,在题目要求后面作答:

代码要求有注释,代码中适当标注关键代码为红色。

要有运行结果的截图。

每题最后应该有对程序的适当分析和总结!

注意格式排版,内容分析注意列条目,展开清楚地阐述。

1、分析理解多个进程的创建

1)若一个程序中有这样的代码,则有几个进程,父子关系如何?

#include
#include
#include
#include

int main(void){
         pid_t pid,pid2;
         char *message;
         int x;
         pid = fork();
pid2 = fork();
         if (pid < 0)
         {       perror("fork failed");
                  exit(1);     }

    补充完整后面的代码,使多个进程输出各自身份,并符合正确的亲缘关系

     if (  )
     {       message = "This is the child\n";
             x = 0;       }

     ……
     printf("%s I'm %d, x=%d
my father is:%d\n",message,x,getpid(),getppid());
     return 0;
}//main

给出代码及执行效果抓图,并要有说明分析!!!!谁先谁后,为什么。 

代码如下:

#include 
#include 
#include 
#include 

int main(void)
{
	pid_t pid,pid2;
	char *message;
	int x;
	pid = fork();
	pid2 = fork();
	if (pid < 0)
	{
		perror("fork failed");
		exit(1);
	}
	if(pid>0 && pid2>0)
	{//父进程
		message = "This is the father\n";
		x = 1;
	}else if(pid>0 && pid2==0)
	{//子进程2
		message = "This is the second child\n";
		x = 2;
	}else if(pid==0 && pid2>0)
	{//子进程1
		message = "This is the first child\n";
		x = 3;
	}else if(pid==0 && pid2==0)
	{//孙进程
		message = "This is the grandson\n";
        	x = 4;
    }
	printf("%s I'm %d, x=%d,my father is:%d\n",message,x,getpid(),getppid());
	return 0;
}

执行效果如下:

当前父进程(7724)的父进程先运行(4258),在父进程(7724)的运行过程中产生了子进程,pid=0,pid2>0产生子进程17725);pid>0,pid2=0产生子进程27726),而pid=0,pid2=0使得子进程17725)又产生了新的子进程(7727);但由于父进程pid=0产生子进程17725)同时运行pid2,子进程2和孙进程(7727)顺序不能确定


2)若有如下的代码

for(i = 0; i <4; i++)
    {
        pid = fork();
……

请分析将产生多少个进程?要有说明分析!!!!

代码如下:

#include 
#include 
#include 
#include 

int main(void)
{
	pid_t pid;
	for(int i = 0; i < 4; i++)
	{
		pid = fork();
	}
	printf("%d\n",getpid());
	return 0;
}

效果如下:

产生16个进程

调用一次fork()函数将得到两个返回值,而两个进程继续执行下一条fork(),则返回4个进程ID的值,依次可得到执行第ifork函数后有2i次方个进程


2、解释执行效果

若有下面的功能代码,执行会有怎样的输出?不只有抓图,要有说明分析!!!!谁先谁后,哪里停顿过。

int main(void){
         pid_t a;
         a = fork();
         if (a == 0) {
             sleep(2);
             execlp ("ps" ,"ps",NULL);      
    printf("%s I'm %d, x=%dmy father is:%d\n",message,getpid(),getppid());
         }
         else 
    printf("%s I'm %d, x=%dmy father is:%d\n",message,getpid(),getppid());

return 0;
}

代码如下:

执行如下图:

执行父进程(6393)时,语句直接执行;当a=0执行其子进程时先执行sleep休眠两秒,然后执行execlp运行ps展示当前进程,覆盖子进程的内容,因此不再执行printf语句。


3. 体验进程/线程的顺序控制。(基于对例5-9,5-11的理解,注意不是用sleep控制)

1)编写一个可产生父子进程的程序,使执行时子进程先printf输出内容,然后父进程再printf输出。

代码如下:

#include
#include
#include
#include
#include


int main(void)
{
    pid_t pc1, pw1;
    pc1 = fork();
    if(pc1 < 0){
        perror("fork failed");
        exit(1);
    }
    if(pc1 > 0){ //父进程
        pw1 = wait(NULL); //进入阻塞状态,等待其有亲缘的进程死亡后重新唤醒
        printf("Catch a dead child process with pid:%d\n",pw1);
        printf("I'm %d, The father process\n",getpid());
    }else{
        printf("I'm %d, The child process,my father process is %d\n",getpid(),getppid());
    }
    return 0;
}

效果如下:

 pc1父进程运行时,当pw1 = wait(NULL)时,系统调用会使调用它的进程等待,进入阻塞状态,等待其有亲缘的子进程死亡进入僵死状态时,系统会重新把它从阻塞态唤醒,因此当父进程阻塞,子进程运行printf语句,运行完毕后子进程死亡,父进程被唤醒,执行printf语句

2)编写一个可产生两个线程(一个输出AAAAAA,一个输出BBBBBB)的程序,代码中要求控制线程的输出为AAAAAABBBBBB的顺序。然后修改控制代码,重新编译执行,得到另一种输出顺序,得到BBBBBBAAAAAA的输出。

代码如下:

#include 
#include 
#include 
#include 
#include 

char message[50] = "Hello World";
void *thread_function2(void *arg)
{
    printf("AAAAAA");
    pthread_exit("子线程结束");
}
void *thread_function1(void *arg)
{
    printf("BBBBBB");
    pthread_exit("子线程结束");
}
 
int main(void)
{
    int res1, res2;
    pthread_t thread1, thread2;
    void *thread_result1, *thread_result2;
    res1 = pthread_create(&thread1,NULL,thread_function1,(void*)message);
    res2 = pthread_create(&thread2,NULL,thread_function2,(void*)message);
    if(res1 != 0 || res2 != 0)
    {
        perror("Thread creation failed!");
        exit(EXIT_FAILURE);
    }
    res1 = pthread_join(thread1,&thread_result1);
    res2 = pthread_join(thread2,&thread_result2);
    if(res1 != 0 || res2 != 0)
    {
        perror("Thread join failed!\n");
        exit(EXIT_FAILURE);
    }
    printf("\n");
    exit(EXIT_FAILURE);
}

 编译执行 效果如下:

修改代码:修改代码只需调换void() 中thread_function1和2的顺序即可

重新编译执行效果如下:

由主进程产生一个线程,线程的功能为修改进程变量message字符串里的内容,利用join使主线程阻塞等待子线程结束返回后才继续执行,从而控制了两者的执行顺序。


4. 体验线程对共享变量的处理应互斥,否则会出现线程不安全问题。(选做)

两个线程对共享的进程变量做加1 *** 作,结果加和是错的(例5-12),多次运行测试效果,体会程序执行原理,给出认识总结。

#include 
#include 
#include 
#include 
#include 

long x = 0, y = 0, z = 0;
void * thread_function1(void *arg)
{
    printf("===thread_function1 is running. \n");
    while(1)
    {
        x++;
        z++;        }//while
    pthread_exit("===Thank you for your CPU time!");
}
void * thread_function2(void *arg)
{
    printf("===thread_function2 is running. \n");
    while(1)
    {
        y++;
        z++;        }//while
    pthread_exit("===Thank you for your CPU time!");
}

int main(){
    int res;
    pthread_t thread1;
    pthread_t thread2;
    void *thread_result;
    res = pthread_create(&thread1,NULL,thread_function1,NULL);
    res = pthread_create(&thread2,NULL,thread_function2,NULL);
    
    sleep(1);

    printf("x = %d\ny = %d\nx+y = %d\nz = %d\n",x,y,x+y,z);
    exit(EXIT_FAILURE);
}

代码功能很简单,主进程定义了三个变量x、y、z,主进程运行产生的两个线程共享这些变量 ,thread1和thread2分别持续对x和y做加1 *** 作,但同时两者每次做加法,也对z做累加1的 *** 作。主线程睡眠1秒后打印输出结果,然后进程结束。

执行效果如下:


多次运行测试:

两线程获得的CPU时间不确定,所以加法做的次数不同,x不等于y没什么问题;但在每次x加1、y加1时,它们也都使z加1了,正常逻辑下z的值和x+y的值应是相等的。

问题实际上出在双方对z的共同 *** 作上。对z的加法 *** 作在系统底层涉及从内存取到寄存器、再在寄存器累加、再保存回变量的内存的过程。两个线程都对z变量的内容做加法,由于线程并发执行,它们的加法 *** 作相互穿插,难免出现其中一个的加法是在另一个加到一半的基础上做的,在错误的基础上做工作很容易出现错误的可能。

解决的办法是,需要提供控制手段,在多方对某种共享资源具有竞争关系时,保证只有一个使用者用完资源后另一个才能使用,这就是典型的资源互斥处理问题。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1295968.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存