ZUCC_操作系统_Lab4线程的创建与管理

Lab 4线程的创建与管理

代码在页底

一.“hello world"单线程

#include<stdio.h>
int main(void){
	p_msg("hello ");
	p_msg("world\n"); 
	return 0;
}

void p_msg(char *s){
	int  i;
	for(i=0;i<5;i++){
		printf("%s",s);
		fflush(stdout);
		sleep(1);
	}
}

1)此例程使用函数调用,理解函数调用是顺序执行的;

1.表现形式:先打印 hello,再打印world

2)理解fflush的作用

fflush():
1.单词释义:flush:冲洗 	fflush:刷新缓冲区
2.作用:清洗读写缓冲区,立即物理写入输出缓冲区的数据
3.类型:
	fflush(stdin):刷新输入缓冲区,将输入缓冲区的数据丢弃
	fflush(stdout):刷新输出缓冲区,将输出缓冲区数据打印到输出设备	
	
4.fflush(out)在源程序中作用:
	sleep(1)使得进程休眠,但是输出的字符串已经保留到缓冲区
	fflush(out)表示,尽管在休眠状况下,直接将缓冲区的数据打印到输出设备(显示屏)
5.实例:
	去掉fflush(out),实验结果:等五秒后电脑一次打印完”hello hello hello hello hello world“
	但是改写printf("hello")->printf("hello\n"),去掉fflush(out)仍就是每秒打印一次"hello"


二."hello world"多线程

易错提醒:
1.代码:pthread_create(&t1,NULL,p_msg,(void*)“hello”);
–>pthread_create(&t1,NULL,(void*)p_msg,(void*)“hello”);
2.编译: gcc -o t t.c -lpthread
-lpthread 链接线程库

#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){
	char *cp=(char*) m;
	int i;
	
	for(i=0;i<5;i++){
		printf("%s",m);
		fflush(stdout);
		sleep(1);	
	}
	return NULL;
}
int main(void){
	pthread_t t1,t2;
	void *p_msg(char*);
		
	pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");	//注意:p_msg->(void *)p_msg
	pthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n");	//
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	return 0;
}

1)从输出内容,观察线程的并发执行

1.  源程序先书写调用“hello"的函数,再书写调用"world"函数
	但图中两次不同的输出显示,hello与world输出先后顺序不定,
	从而表示了两个线程是并发执行的
2.	并发:一定时间段内,多个程序交替使用CPU,但在任意时间点上只有一个程序在CPU上运行


2)运行例程,用ps -aL ,ps -efl,pstree -p观察线程信息

1.ps -aL

ps -aL
1.命令提示符:
	ps -a:选择除两个会话头之外的所有进程以及与终端无关的进程
	ps -L:显示线程
	ps -aL:显示除两个会话头之外的所有进程以及与终端无关的进程的线程
注意:注意大小写,不能用ps -al
	ps -l表示长格式,不会显示线程相关信息(LWP值)

2.线程相关信息:
	PID: process identification		进程标识符
	LWP: light weight process	 	轻量级进程(线程)
	TTY: teletypes              	虚拟控制台,串口以及伪终端设备组成的终端设备
	CMD: command prompt     	  	命令提示符

ps -aL


2.ps -efL

ps -efL
1.命令提示符:
	ps -e:  显示所有进程			与ps -A一致
	ps -f:  做清单列表
	ps -L:  显示线程
	ps -efL:显示所有进程与线程的清单列表

2.线程相关信息:
	UID: User ID						用户ID
	PPID:Parent Process ID				父进程ID
	NLWP: The number of LWP				 一个进程中线程的数量
	STIME Start Time					 进程开始的时间

ps -efL


3.pstree -p

pstree -p
1.命令提示符:
	pstree -p: 显示进程树,在进程后括号内十进制表示PID

pstree -p


4.线程信息

线程信息:
1.命令提示符:
	gcc -lpthread:  链接线程函数库
	grep p1:		检索与p1相同的字符串的内容	
	./p1 > p1.txt:	将p1执行的结果保存在p1.txt文件中
	./p1 |ps -L:	表示只统计执行p1时,相关的线程信息,"|"表示同时


5.遇到的小小问题:

gcc编译书上代码时会有提示出错,但仍可生成可执行文件?

解决方式:主函数:
	pthread_create(&t1,NULL,p_msg,(void*)"hello");
  ->pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");


3)理解pthread_create()函数的作用

1.头文件:#include<pthread.h>
2.函数声明:
	int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*),void *arg)
3.参数说明:
	pthread_t *thread:				指向线程的标识符,返回线程的ID
	pthread_attr_t *attr:			设置线程的属性,NULL表示默认属性
	void *(*start_routine)(void*):	线程运行函数的起始地址
	void *arg:						传给线程启动函数的参数
4.返回值:
	成功创建返回0,失败返回错误码
5.pthread_create()函数说明:
	是类UNIX操作系统(Unix,Linux,Mac OSX等)创建线程的函数
	创建线程,本质是确定调用该线程函数的入口点
	线程创建后,就开始运行相关线程函数

4)理解pthread_join()函数的作用;删除pthread_join函数后,观察例程运行

1.头文件:#include<pthread.h>
2.函数声明:
	int pthread_join(pthread_t thread,void **retval)
3.参数说明:
	pthread_t thread:		线程标识符,即线程ID					   thread:线		pthread:线程 
	void **retval:			用户定义的指针,用来储存被等待线程的返回值  retval:return value
4.返回值:
	成功等待返回0,失败返回错误值
5.pthread_join()函数说明:
	A线程调用B线程并执行pthread_join()后,A线程处于阻塞状态
	当B线程结束,pthread_join()函数返回,回收B线程的资源,A线程才继续执行

1.结果:
	1.不使用pthread_join()函数,主线程可以输出,但子线程不会有输出
	2.仅有一个pthread_join()函数,也会有一样的输出

2.删除一个pthread_join:

3.删除两个pthread_join:

4.相关链接:

详情链接


5)改写代码,用并发的父子进程输出同样的内容,体会进程和线程的差别

#include <stdio.h>
#include <stdlib.h>

int main(void)  {
	int i;
	int pid[5];
	
	for(i=0;i<5;i++)
		if((pid[i]=fork())==0){
			printf("hello");
			exit(0);	
		}	
		else{
			wait(0);
			printf("world\n");
		}
	return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详

0进程线程
1.创建fork()pthread_create()
2.等待终止wait()pthread_join()
3.包括程序,数据,TCB程序,数据,堆栈,PCB
4.调度,执行调度,执行;拥有资源所有权



三.线程的并发执行和ID状态

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);

int main(void){
	int i,ret;
	pthread_t a_pthread;
	int thread[num];
		
	for(i=0;i<num;i++)
		thread[i]=i;
	for(i=0;i<num;i++){
		ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
		if(ret==0)
			printf("Pthread launch successfully!\n");
	}
	i=getchar();
	return 0;
}
void *PrintThread(void *n){
	int i;
	for(i=0;i<3;i++){
		printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
		sleep(1);	
	}
	return NULL;
}

1)分析输出内容不连续的原因

由图可知,pid均为10537,lwp有所不同,表明运行的所有线程均在同一进程下
多线程共用同一进程的资源
各线程在调用PrintThread()时每次输出后sleep(1)休眠1s,该线程进入阻塞态
在其休眠期间,其他线程由就绪态->运行态,执行程序并输出


2)删除sleep(1),观察变化;分析原因

现象:每个线程内容连续输出
原因:每个线程在创建后进入就绪态,按照系统调用依次执行各线程,
	 无sleep()和pthread_join()使用,线程正常执行,不会中断,直到线程正常结束,由运行态->死亡态	
	 待一个线程结束后,按系统调度将就绪态中一个线程执行,变为运行态


3)分析输出内容,理解线程各种ID的含义

1.相关信息:
	num : 创建的第几个线程
	pid : 进程ID
	lwp : 线程			(light weight process:轻量级进程(线程))
	tid : 线程ID		(thread ID)(与lwp同表示线程,值不同)
2.内容分析:
	1.3个线程均成功创建	Pthread launch successfully!且仅有10538,10539,10540三个线程号
	2.共用一个进程	 	 pid均为10537



四.POSIX互斥锁的使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>

int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);

int main(void){
	pthread_t p1=0;
	pthread_t p2=0;
	int ret=0;
	
	printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());
	pthread_mutex_init(&mutex,NULL);
	ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=
	ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	
	printf("main program is exited\n");
	return 0;
}
static void pthread_add2(void){
	int i;

	printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
	for(i=0;i<3;i++){
//		pthread_mutex_lock(&mutex);
		gnum++;
		sleep(1);
		gnum++;
		printf("pthread_add 2 gnum is %d \n",gnum);
//		pthread_mutex_unlock(&mutex);	
	}
	pthread_exit(NULL);			//**
}
static void pthread_add3(void){		//**
	int i;

	printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
	for(i=0;i<4;i++){
//		pthread_mutex_lock(&mutex);
		gnum++;
		sleep(1);
		gnum++;
		sleep(1);
		gnum++;
		printf("pthread_add 3 gnum is %d \n",gnum);		//gum
//		pthread_mutex_unlock(&mutex);					//unlock
	}
	pthread_exit(0);
}

1)保留注释,运行程序,输出混乱,分析原因

1.  由图可知,进程ID为12615的进程创建了两个线程:12616,12617.
	两个线程并发执行调用进程的资源
	当一个线程调用pthread_add2并执行sleep(1)休眠时,该线程进入阻塞期
	另一个线程由就绪态,被调用执行成运行态,执行pthread_add3并输出
	依次两个线程不断运行->阻塞,就绪->运行交替,所以输出内容交替


2)删除sleep()函数,运行程序,混乱消失,思考是否从根本上解决问题

1.	没有
	图1所示,同一进程产生的两个线程由系统调度,依次顺序执行全部程序和输出,并结束
	但是,如图2所示,由于线程是并发执行的,线程的调度由操作系统执行,
	所以同一优先级线程调度非顺序,导致最后输出结果gnum不一致

图1

图2

3)恢复sleep()函数,删掉注释,使用互斥锁,观察结果,理解互斥锁的作用

结果:
	1.优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"
	2.完整的执行完一个线程后,再执行另一个线程
互斥锁的作用:
	1.使得同一优先级线程可以按照顺序执行各线程
	2.当一个线程调用互斥锁时,其他请求锁的线程会进入等待队列,待锁释放后,按照优先级获得锁
疑问:
	1.为啥优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"?

图1


4)掌握互斥锁的基本操作:初始化,加锁(P操作),解锁(V操作)

1.全局变量:		pthread_mutex_t mutex
2.动态互斥锁初始化:   pthread_mutex_init(pthread_mutex_t *restrict mutex,pthread_mutexattr_t *restrict attr)
3.加锁:	          pthread_mutex_lock(pthread_mutex_t *mutex)
4.解锁:	          pthread_mutex_unlock(pthread_mutex_t *mutex)

5)输出内容不在混乱,分析互斥锁与删除sleep()不同的原因

1.相同:不用sleep()和用互斥锁都可以使得输出内容有序
2.不同:不用sleep():		线程并发的一次执行完成.
				   		A线程,B线程输出先后顺序不一定
		用互斥锁:		线程A先创建,先开启互斥锁,线程B创建时申请锁的使用进入等待队列
						只有线程A释放锁后,线程B才能开始执行.A.B线程输出顺序一定

6)使用互斥锁修改例程3,使得输出结果不交错

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;

int main(void){
	int i,ret;
	pthread_t a_pthread;
	int thread[num];
		
	pthread_mutex_init(&mutex,NULL);
	for(i=0;i<num;i++)
		thread[i]=i;
	for(i=0;i<num;i++){
		ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
		if(ret==0)
			printf("Pthread launch successfully!\n");
	}
	i=getchar();
	return 0;
}
void *PrintThread(void *n){
	int i;
	for(i=0;i<3;i++){
		pthread_mutex_lock(&mutex);
		printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
		sleep(1);	
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}


五.POSIX互斥锁

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){
	int i,j;		//i,j不能混用
	for(i=0;i<20;i++){
//		pthread_mutex_lock(&mutex);
		j=myglobal;
		j++;
		printf(".");
		fflush(stdout);
		usleep(1000);
		myglobal=j;	
//		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}
int main(void){
	int i;
	pthread_t mythread=0;
	
	pthread_mutex_init(&mutex,NULL);
	if(pthread_create(&mythread,NULL,thread_function,NULL)){
		printf("error creating mythread\n");
		abort();		//**abort(0)
	}
	for(i=0;i<20;i++){
//		pthread_mutex_lock(&mutex);
		myglobal++;
		printf("O");
		fflush(stdout);
		usleep(1000);
//		pthread_mutex_unlock(&mutex);
	}
	if(pthread_join(mythread,NULL)){
		printf("error joining mythread\n");
		abort();
	}
	printf("\n myglobal equald is %d\n",myglobal);		//**
	pthread_mutex_destroy(&mutex);
	exit(0);
}

1)观察程序加锁前后的变化,理解互斥锁的使用

1.abort()
	释义:	abort:终止
	头文件:#include<stdlib.h>
	作用:	异常终止当前进程
2.互斥锁的使用:
	效果:使得线程依次顺序执行,避免并发导致的的输出混乱,单一输出的不完整
3.疑问:
	1.为啥先输出‘0’,再输出'.'?(为啥主函数的线程先拿到了锁)




六.代码区

1.“hello world"单线程

#include<stdio.h>
int main(void){
	p_msg("hello ");
	p_msg("world\n"); 
	return 0;
}

void p_msg(char *s){
	int  i;
	for(i=0;i<5;i++){
		printf("%s",s);
		fflush(stdout);
		sleep(1);
	}
}

2."hello world"多线程

#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){
	char *cp=(char*) m;
	int i;
	
	for(i=0;i<5;i++){
		printf("%s",m);
		fflush(stdout);
		sleep(1);	
	}
	return NULL;
}
int main(void){
	pthread_t t1,t2;
	void *p_msg(char*);
		
	pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");	//注意:p_msg->(void *)p_msg
	pthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n");	//
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	return 0;
}

2-1.用并发的父子进程输出同样的内容

#include <stdio.h>
#include <stdlib.h>

int main(void)  {
	int i;
	int pid[5];
	
	for(i=0;i<5;i++)
		if((pid[i]=fork())==0){
			printf("hello");
			exit(0);	
		}	
		else{
			wait(0);
			printf("world\n");
		}
	return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详

3.线程的并发执行和ID状态

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);

int main(void){
	int i,ret;
	pthread_t a_pthread;
	int thread[num];
		
	for(i=0;i<num;i++)
		thread[i]=i;
	for(i=0;i<num;i++){
		ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
		if(ret==0)
			printf("Pthread launch successfully!\n");
	}
	i=getchar();
	return 0;
}
void *PrintThread(void *n){
	int i;
	for(i=0;i<3;i++){
		printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
		sleep(1);	
	}
	return NULL;
}

4.POSIX互斥锁的使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>

int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);

int main(void){
	pthread_t p1=0;
	pthread_t p2=0;
	int ret=0;
	
	printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());
	pthread_mutex_init(&mutex,NULL);
	ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=
	ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);
	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	
	printf("main program is exited\n");
	return 0;
}
static void pthread_add2(void){
	int i;

	printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
	for(i=0;i<3;i++){
//		pthread_mutex_lock(&mutex);
		gnum++;
		sleep(1);
		gnum++;
		printf("pthread_add 2 gnum is %d \n",gnum);
//		pthread_mutex_unlock(&mutex);	
	}
	pthread_exit(NULL);			//**
}
static void pthread_add3(void){		//**
	int i;

	printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
	for(i=0;i<4;i++){
//		pthread_mutex_lock(&mutex);
		gnum++;
		sleep(1);
		gnum++;
		sleep(1);
		gnum++;
		printf("pthread_add 3 gnum is %d \n",gnum);		//gum
//		pthread_mutex_unlock(&mutex);					//unlock
	}
	pthread_exit(0);
}

4-1.使用互斥锁修改例程3,使得输出结果不交错

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;

int main(void){
	int i,ret;
	pthread_t a_pthread;
	int thread[num];
		
	pthread_mutex_init(&mutex,NULL);
	for(i=0;i<num;i++)
		thread[i]=i;
	for(i=0;i<num;i++){
		ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
		if(ret==0)
			printf("Pthread launch successfully!\n");
	}
	i=getchar();
	return 0;
}
void *PrintThread(void *n){
	int i;
	for(i=0;i<3;i++){
		pthread_mutex_lock(&mutex);
		printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
		sleep(1);	
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}

5.POSIX互斥锁

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){
	int i,j;		//i,j不能混用
	for(i=0;i<20;i++){
//		pthread_mutex_lock(&mutex);
		j=myglobal;
		j++;
		printf(".");
		fflush(stdout);
		usleep(1000);
		myglobal=j;	
//		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}
int main(void){
	int i;
	pthread_t mythread=0;
	
	pthread_mutex_init(&mutex,NULL);
	if(pthread_create(&mythread,NULL,thread_function,NULL)){
		printf("error creating mythread\n");
		abort();		//**abort(0)
	}
	for(i=0;i<20;i++){
//		pthread_mutex_lock(&mutex);
		myglobal++;
		printf("O");
		fflush(stdout);
		usleep(1000);
//		pthread_mutex_unlock(&mutex);
	}
	if(pthread_join(mythread,NULL)){
		printf("error joining mythread\n");
		abort();
	}
	printf("\n myglobal equald is %d\n",myglobal);		//**
	pthread_mutex_destroy(&mutex);
	exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值