HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁

目的

1)理解进程/线程的概念和应用编程过程;
2)理解进程/线程的同步机制和应用编程;

任务

1)在Linux下创建一对父子进程。
2)在Linux下创建2个线程A和B,循环输出数据或字符串。
3)在Windows下创建线程A和B,循环输出数据或字符串。
4)在Linux下创建一对父子进程,实验wait同步函数。
5)在Windows下利用线程实现并发画圆/画方。
6)在Windows或Linux下利用线程实现“生产者-消费者”同步控制
7)在Linux下利用信号机制实现进程通信。
8)在Windows或Linux下模拟哲学家就餐,提供死锁和非死锁解法。
1/6/8选做,其余任选其一。

任务1 在Linux下创建一对父子进程。

1. 提示

提示1:分别输出各自的进程号父进程号特别的提示字符串信息
提示2:让父进程提前结束或后结束,观察子进程的父进程ID
提示3:使用PS命令查看进程列表信息,核对进程号,父进程号

2. 任务代码

#include
#include
int main()
{
	printf("'-'代表父进程输出信息;'+'代表子进程输出信息。\n");
	pid_t p1=fork();
	if(p1){
		printf("- 1. 父进程进程号:%d\n",getpid());
                printf("- 1. 创建的子进程进程号:%d\n",p1);
		printf("- 3. 暂时挂起父进程以便ps检查结果(10s):\n");
		sleep(10);
		printf("- 2. 父进程结束\n");
	}else{
		printf("+ 1. 子进程进程号:%d\n",getpid());
		printf("+ 1. 父进程未结束时,子进程的父进程进程号:%d\n",getppid());
		printf("+ 3. 暂时挂起子进程以便ps检查结果(11s):\n");
		sleep(11);
		printf("+ 2. 父进程结束后,子进程的父进程进程号:%d\n",getppid());
		printf("+ 3. 子进程继续挂起,同时再用ps检查结果(10s):\n");
                sleep(10);       //为了让子进程更晚结束
		printf("+ 3. 子进程结束\n");
	}
	return 0;
}

ps检查指令(我用gcc编译后的程序名称叫做a.out):

ps -ef|grep a.out

3. 结果及说明

HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第1张图片
图中输出信息的标号分别对应不同的任务提示。
图中ps结果的第一列为用户名,第二列为pid,第三列为父进程pid。

  1. 第一次ps检查时:父进程号为27236,子进程号为27237,且子进程的父进程号为27236。
  2. 第二次ps检查时:父进程已经结束,子进程挂起,它的父进程号变为1。即交给init程序。
  3. 第三次ps检查时:父子进程都结束,ps显示没有27236、27237的进程在运行。

但是程序似乎并没有结束。为啥?

任务6 在Windows或Linux下利用线程实现“生产者-消费者”同步控制

1. 提示

提示1:使用数组(10个元素)代替缓冲区。2个输入线程产生产品(随机数)存到数组中;3个输出线程从数组中取数输出。
提示2: Windows使用临界区对象和信号量对象,主要函数
EnterCriticalSection | LeaveCriticalSection | WaitForSingleObject | ReleaseSemaphore
提示3:Linux使用互斥锁对象和轻量级信号量对象,主要函数:
sem_wait( ),sem_post( ),pthread_mutex_lock( ),
pthread_mutex_unlock( )
提示4:生产者1的数据:1000-1999 (每个数据随机间隔100ms-1s),生
产者2的数据:2000-2999 (每个数据随机间隔100ms-1s)
提示5:消费者每休眠100ms-1s的随机时间消费一个数据。
提示6:屏幕打印(或日志文件记录)每个数据的生产和消费记录。

2. 任务代码

Linux:

#include
#include
#include
#include
#include
#include
#include
int share[10];	//共享缓冲区
int indexc=0;	//生产者处理的标号(临界资源)
int indexp=0;	//消费者处理的标号(临界资源)
sem_t is_empty;	//缓冲区中存在空位
sem_t is_full;	//缓冲区已满
pthread_mutex_t mutexc = PTHREAD_MUTEX_INITIALIZER;	//互斥锁
pthread_mutex_t mutexp = PTHREAD_MUTEX_INITIALIZER;     //互斥锁

void* consume(void* arg){
	int add=(int)arg;
        while(1){
		sem_wait(&is_full);
                pthread_mutex_lock(&mutexc);
                struct timespec ts;
                clock_gettime(CLOCK_MONOTONIC, &ts);
                int data=ts.tv_nsec%1000+add;
                share[indexc]=data;
                float sleepTime=0.001*(ts.tv_nsec%900)+0.1;
                printf("生产者%ld填第%d块:%d,即将沉睡%fs\n",syscall(SYS_gettid),indexc,data,sleepTime);
		indexc++;
                if(indexc>9)indexc-=10;
		pthread_mutex_unlock(&mutexc);
                sem_post(&is_empty);
                sleep(sleepTime);

        }
}

void* produce(void){
	while(1){
		sem_wait(&is_empty);
                struct timespec ts;
                clock_gettime(CLOCK_MONOTONIC, &ts);
		float sleepTime=0.001*(ts.tv_nsec%900)+0.1;
		pthread_mutex_lock(&mutexp);
                printf("消费者%ld取第%d块:%d, 即将沉睡%fs\n",syscall(SYS_gettid),indexp,share[indexp],sleepTime);
                indexp++;
                if(indexp>9)indexp-=10;
                pthread_mutex_unlock(&mutexp);
		sem_post(&is_full);
                sleep(sleepTime);
        }

}

int main(){
	sem_init(&is_empty,0,0);//初值为0,用于同步生产者和消费者
	sem_init(&is_full,0,9);	//初值为9,表示初始缓冲区填10个满
	pthread_t idc1,idc2;
	pthread_t idp1,idp2,idp3;
	pthread_create(&idc1,NULL,(void*)consume,(void*)1000);
	pthread_create(&idc2,NULL,(void*)consume,(void*)2000);
	pthread_create(&idp1,NULL,(void*)produce,NULL);
	pthread_create(&idp2,NULL,(void*)produce,NULL);
	pthread_create(&idp3,NULL,(void*)produce,NULL);
	pthread_join(idc1,NULL);
	pthread_join(idc2,NULL);
	pthread_join(idp1,NULL);
	pthread_join(idp2,NULL);
	pthread_join(idp3,NULL);
	return 0;
}

3. 结果及说明

HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第2张图片
图中的生产者是29108、29109,消费者是29110、29111、29112。沉睡时间为100ms到1s,填入数据随机。

使用了两个信号量is_empty/is_full,两个互斥锁mutexc和mutexp。
其中信号量用于监测缓冲区是否还能继续填入或已满,互斥锁分别用于生产者、消费者的互斥使用临界资源indexc和indexp操作。

任务8 在Windows或Linux下模拟哲学家就餐,提供死锁和非死锁解法。

1. 提示

提示1:同时提供提供可能会带来死锁的解法和不可能死锁的解法。
提示2:可能会带来死锁的解法参见课件。Windows尝试使用临界区对象(EnterCriticalSection,LeaveCriticalSection);Linux尝试使用互斥锁(pthread_mutex_lock, pthread_mutex_unlock)
提示3:完全不可能产生死锁的解法,例如:尝试拿取两只筷子,两只都能拿则拿,否则都不拿。 Windows 尝试使用
WaitForMultipleObjects, WaitForSingleObject和互斥量对象
ReleaseMutex等相关函数) Linux尝试使用互斥锁pthread_mutex_lock,pthread_mutex_trylock等函数。
提示4:[可选]图形界面显示哲学家取筷,吃饭,放筷,思考等状态。
提示5:为增强随机性,各状态间维持100ms-500ms内的随机时长。

2. 死锁任务

死锁代码

#include
#include
#include
#include
#include
#include
#include
#define thinker_num 5
pthread_mutex_t s[thinker_num];	//筷子

float getRandTime(){		//100ms~500ms
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC,&ts);
        return 0.001*(ts.tv_nsec%400)+0.1;
}

void* thinker(void* arg){
	int index=(int)arg;
	int index2=(index+1)%thinker_num;
	float sleepTime;
        while(1){
		sleepTime=getRandTime();
		printf("哲学家%ld正在思考,需时%fs\n",syscall(SYS_gettid),sleepTime);
		sleep(sleepTime);
                sleepTime=getRandTime();
                printf("哲学家%ld正在休息,需时%fs\n",syscall(SYS_gettid),sleepTime);
                sleep(sleepTime);
		pthread_mutex_lock(&s[index]);
		printf("哲学家%ld拿起筷子%d\n",syscall(SYS_gettid),index);
		pthread_mutex_lock(&s[index2]);
		sleepTime=getRandTime();
		printf("哲学家%ld拿起筷子%d,%d,开始吃饭,需时%fs\n",syscall(SYS_gettid),index,index2,sleepTime);
		sleep(sleepTime);
		pthread_mutex_unlock(&s[index2]);
		printf("哲学家%ld放下筷子%d\n",syscall(SYS_gettid),index2);
		pthread_mutex_unlock(&s[index]);
		printf("哲学家%ld放下筷子%d,%d\n",syscall(SYS_gettid),index,index2);
        }
}

int main(){
	pthread_t id[thinker_num];
	for(int i=0;i<thinker_num;++i)
		pthread_create(&id[i],NULL,(void*)thinker,(void*)i);
	for(int i=0;i<thinker_num;++i)
		pthread_join(id[i],NULL);
	return 0;
}

运行结果

HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第3张图片
解释如图。

3. 不死锁任务

不死锁代码

#include
#include
#include
#include
#include
#include
#include
#define thinker_num 5
pthread_mutex_t s[thinker_num];	//筷子
sem_t s_full;			//最多(thinker-1)个人同时开始吃饭

float getRandTime(){		//100ms~500ms
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC,&ts);
        return 0.001*(ts.tv_nsec%400)+0.1;
}

void* thinker(void* arg){
	int index=(int)arg;
	int index2=(index+1)%thinker_num;
	float sleepTime;
        while(1){
		sleepTime=getRandTime();
		printf("哲学家%ld正在思考,需时%fs\n",syscall(SYS_gettid),sleepTime);
		sleep(sleepTime);
                sleepTime=getRandTime();
                printf("哲学家%ld正在休息,需时%fs\n",syscall(SYS_gettid),sleepTime);
                sleep(sleepTime);
		sem_wait(&s_full);
		pthread_mutex_lock(&s[index]);
		printf("哲学家%ld拿起筷子%d\n",syscall(SYS_gettid),index);
		pthread_mutex_lock(&s[index2]);
		sleepTime=getRandTime();
		printf("哲学家%ld拿起筷子%d,%d,开始吃饭,需时%fs\n",syscall(SYS_gettid),index,index2,sleepTime);
		sleep(sleepTime);
		pthread_mutex_unlock(&s[index2]);
		printf("哲学家%ld放下筷子%d\n",syscall(SYS_gettid),index2);
		pthread_mutex_unlock(&s[index]);
		printf("哲学家%ld放下筷子%d,%d\n",syscall(SYS_gettid),index,index2);
		sem_post(&s_full);
        }
}

int main(){
	sem_init(&s_full,0,thinker_num-2);	//初值为3,说明最多4个同时开始拿筷子
	pthread_t id[thinker_num];
	for(int i=0;i<thinker_num;++i)
		pthread_create(&id[i],NULL,(void*)thinker,(void*)i);
	for(int i=0;i<thinker_num;++i)
		pthread_join(id[i],NULL);
	return 0;
}

运行结果

HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第4张图片
增设初值为3的信号量s_full(信号量为0到3时该线程都可以继续执行),保证同一时间开始拿筷子的思考家少于5个,破坏死锁必要条件中的环路条件。

任务5 在Windows下利用线程实现并发画圆/画方。

1. 提示

提示1:圆心,半径,颜色,正方形中心,边长,颜色自己确定。
提示2:圆和正方形边界建议都取720个点。为直观展示绘制过程,每个
点绘制后睡眠0.2秒~0.5秒。
提示3:建议使用VS和MFC或QT对话框类型程序来绘制窗口和图形。

2. 任务代码

环境:Windows10 、VS2019。
进入下面这个网站安装图形库(天地良心,这是我见过的最好安的东西)。
https://www.easyx.cn

其实用原生也能画,但easyx真的酷炫炸了!效果爆炸!还有好多有趣的范例!!!爱了!这可是C++啊!

#include 
#include 

// 使用 Bresenham 画圆法
DWORD Circle_Bresenham(LPVOID lpParam)
{
	int x = 320, y = 240, r = 90, color = LIGHTBLUE;
	int tx = 0, ty = r, d = 3 - 2 * r;

	while (tx <= ty)
	{
		// 利用圆的八分对称性画点
		putpixel(x + tx, y + ty, color);
		putpixel(x + tx, y - ty, color);
		putpixel(x - tx, y + ty, color);
		putpixel(x - tx, y - ty, color);
		putpixel(x + ty, y + tx, color);
		putpixel(x + ty, y - tx, color);
		putpixel(x - ty, y + tx, color);
		putpixel(x - ty, y - tx, color);

		if (d < 0)		// 取上面的点
			d += 4 * tx + 6;
		else			// 取下面的点
			d += 4 * (tx - ty) + 10, ty--;
		Sleep(100);
		tx++;
	}
	return 0;
}

DWORD Square(LPVOID lpParam)
{
	int x = 320, y = 240, r = 90, color = RED;
	int tx = 0, ty = r;

	while (tx <= ty)
	{
		// 利用正方形的八分对称性画点
		putpixel(x + tx, y + ty, color);
		putpixel(x + tx, y - ty, color);
		putpixel(x - tx, y + ty, color);
		putpixel(x - tx, y - ty, color);
		putpixel(x + ty, y + tx, color);
		putpixel(x + ty, y - tx, color);
		putpixel(x - ty, y + tx, color);
		putpixel(x - ty, y - tx, color);
		Sleep(70);	//保证圆和方基本上同时结束
		tx++;
	}
	return 0;
}

// 主函数
int main()
{
	HANDLE hThread[2];
	DWORD ThreadID;

	initgraph(640, 480);

	// 测试画圆
	hThread[0] = CreateThread(NULL, 0, Circle_Bresenham, NULL, 0, &ThreadID);
	// 测试画方
	hThread[1] = CreateThread(NULL, 0, Square, NULL, 0, &ThreadID);
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	// 按任意键退出
	_getch();
	closegraph();
	return 0;
}

3. 结果及说明

HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第5张图片

没有什么特殊的,就是创建两个线程然后描点。我没按提示来。
绘画过程引起极度舒适!!!

为获得更好的视觉效果,使用正方形和圆的八方对称性,四个边八个方向轮流画。
其中正方形和圆使用了不同的线程。

夹带一张别的。好看!!充分体现了并发的相互覆盖。HUST_CSE_OS_EXPERIMENT_2|第4章进程管理,第5章死锁_第6张图片

你可能感兴趣的:(笔记,linux,服务器,运维)