一、实验目的
1、加深对进程概念的理解,明确进程和程序的区别。
2、探索、理解并掌握操作系统同步机制的应用编程方法,针对典型的同步问题,构建基于Windows(或 Linux)操作系统同步机制的解决方案。
二、实验内容
1、熟悉和运用 Linux操作系统中系统调用fork()的功能,编写程序调用fork()创建两个子进程。父进程显示字符串‘Parent:’;两个子进程分别显示字符串‘Child1:’和‘Child2:’。多次运行此程序,观察屏幕显示的结果,并分析原因。
2、了解、熟悉和运用 Windows(或 Linux)操作系统同步机制及编程方法,针对典型的同步问题,譬如生产者-消费者问题、读者优先的读者-写者问题、写者优先的读者-写者问题、读者数限定的读者-写者问题、哲学家就餐问题等(任选一至二个),编程模拟实现相应问题的解决方案。
三、设计原理(或方案)及相关算法
操作系统实验二、典型同步问题模拟处理编程设计与实现https://blog.csdn.net/m0_48958478/article/details/1222367061.由fork创建的新进程被称为子进程。该函数被调用一次,但返回两次。所以先创建一个子进程,然后在父进程中再创建一个子进程,通过输出的内容观察fork函数的用法。
2.1生产者-消费者问题
生产者-消费者问题详解http://blog.chinaunix.net/uid-21411227-id-1826740.html生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。缓冲区是临界资源,各进程必须互斥地访问。2.2读者优先的读者写者问题
读者优先指的是除非有写者在写文件,否则读者不需要等待。所以可以用一个整型变量read_count记录当前的读者数目,用于确定是否需要释放正在等待的写者线程(当read_count=O时,表明所有的读者读完,需要释放写者等待队列中的一个写者)。每一个读者开始读文件时,必须修改read_count变量。因此需要一个互斥对象mutex来实现对全局变量read_count修改时的互斥.另外,为了实现写-写互斥,需要增加一个临界区对象write。当写者发出写请求时,必须申请临界区对象的所有权。通过这种方法,也可以实现读-写互斥,当read_count=1时(即第一个读者到来时),读者线程也必须申请临界区对象的所有权。当读者拥有临界区的所有权时,写者阻塞在临界区对象write上。当写者拥有临界区的所有权时,第一个读者判断完"read_count==1"后阻塞在write上,其余的读者由于等待对read_count的判断,阻塞在mutex上。
2.3写者优先的读者写者问题
写者优先与读者优先类似。不同之处在于一旦一个写者到来,它应该尽快对文件进行写操作,如果有一个写者在等待,则新到来的读者不允许进行读操作。为此应当添加一个整型变量write_count,用于记录正在等待的写者的数目,当write_count=O时,才可以释放等待的读者线程队列。为了对全局变量write_count实现互斥,必须增加一个互斥对象mutex2。为了实现写者优先,应当添加一个临界区对象read,当有写者在写文件或等待时,读者必须阻塞在read上。同样,有读者读时,写者必须等待。于是,必须有一个互斥对象RW_mutex来实现这个互斥。有写者在写时,写者必须等待。读者线程要对全局变量read_count实现操作上的互斥,必须有一个互斥对象命名为mutex1。
2.4哲学家就餐问题
由Dijkstra提出并解决的哲学家进餐问题(The Dinning Philosophers Problem)是典型的同步问题。该问题是描述有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
四、结果分析
编译程序加不加 -lpthread 的区别https://www.cnblogs.com/sky-heaven/p/6840693.htmlgcc编译线程程序,为什么要加-lpthread,头文件已经包含了了啊
包含头文件了,仅能说明有了线程函数的声明, 但是还没有实现, 加上-lpthread是在链接阶段,链接这个库
-lpthread -lm 这两个参数在多线程中起到什么作用? -pthread和-lpthread有什么区别?
lpthread是表示要连接到pthread的库是这里省略的lib,你应该可以找到共享库libpthread.so的。因为pthread编程用到的函数在pthread库里面,就像你使用pow等数学计算函数,需要用到math.h.需要 -lm。
Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)https://blog.csdn.net/qq_38898129/article/details/808272801.从实验结果可以体现出fork()进程被调用一次但返回两次
2.1缓冲区为空时,生产者生产产品。当缓冲区有产品时,消费者消费产品。输入q结束进程
2.2写者线程先到达,开始运行,之后读者线程到达,对文件资源进行P操作,写者线程进入阻塞状态,读者 线程开始运行。
2.3读者线程运行时,写者线程到达,因为写者优先,所以读者进程进入阻塞状态,写者线程运行。
哲学家就餐问题C语言实现(涉及多线程、信号量等)https://blog.csdn.net/qq_44806305/article/details/1068819252.4本程序用五个线程模拟五位哲学家,用五个sem_t型信号量模拟五根筷子(临界资源),解题的思想是:每次让每位哲学家优先拿起自己左边的筷子,并限制最多有四个人同时拿起筷子,从而避免了死锁情况,该程序模拟五个哲学家进餐一次的情况,并重复模拟了1000次以上来检验是否出现死锁情况,经检验程序运行正常,没有出现死锁状况,此处图片仅展示了第1000次的情况。
五、源程序
1.从实验结果可以体现出fork()进程被调用一次但返回两次
vim fork.c
#include <stdio.h>
#include <stdlib.h>
int main(){
int p1,p2;
//由fork创建的新进程被称为子进程( child process)。该函数被调用一次,但返回两次。
//两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID
while((p1=fork())==-1);
if (p1==0)
printf("Child1 \n");
else
{
while((p2=fork())==-1);
if (p2==0)
printf("Child2 \n");
else
printf("Parent \n");
}
return 0;
}
Linux进程创建及同步实验(fork()函数使用,生产者-消费者问题的p,v操作)https://blog.csdn.net/qq_38898129/article/details/80827280
vim fork2.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) { pid_t pid1,pid2; //进程标识符 pid1 = fork(); //创建一个新的进程 if(pid1<0) { printf("创建进程失败!"); exit(1); } else if(pid1==0) //如果pid为0则表示当前执行的是子进程 { printf("子进程1,进程标识符是%d\n",getpid()); } else //否则为父进程 { pid2 = fork();//創建一個新的進 if(pid2<0) { printf("创建进程失败!"); exit(1); } else if(pid2==0) //如果pid为0则表示当前执行的是子进程 { printf("子进程2,进程标识符是%d\n",getpid()); } else //否则为父进程 { printf("父进程,进程标识符是%d\n",getpid()); } } return 0; }
这里的pid_t类似一个类型,就像int型一样,int型定义的变量都是整型的,pid_t定义的类型都是进程号类型。这个语句的意思是定义了一个pid_t类型的变量pid,fork()函数返回一个进程号,这个进程号赋给了pid。
2.1缓冲区为空时,生产者生产产品。当缓冲区有产品时,消费者消费产品。输入q结束进程
vim pc.c
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <sys/types.h>
# include <pthread.h>
# include <semaphore.h>
# include <string.h>
# include <unistd.h>
#define BUFFER_SIZE 5
//empty 同步信号量,表示剩余空间的数量
// full 同步信号量,表示产品的数量
// mutex 互斥信号量,实现对缓冲区的互斥访问
sem_t empty, full, mutex;
typedef int buffer_item;
//缓冲区
buffer_item buffer[BUFFER_SIZE];
int in, out;
// 记录产品的id
int id = 0;
//生产产品
int insert_item(buffer_item item) {
buffer[out] = item;
out = (out + 1) % BUFFER_SIZE;
return 0;
}
//消费产品
int remove_item(buffer_item *item) {
// 将buffer[in]移除,并将item填充进去
*item = buffer[in];
in = (in + 1) % BUFFER_SIZE;
return 0;
}
//生产者
void *producer(void* param) {
long threadid = (long)param;
while (1){
sem_wait(&empty);
sem_wait(&mutex);
//生产产品
insert_item(id);
sleep(2);
printf("ThreadId %ld : Producer produce product %d \n", threadid,id);
id++;
sem_post(&mutex);
sem_post(&full);
}
}
//消费者
void *consumer(void* param) {
long threadid = (long)param;
while (1){
sem_wait(&full);
sem_wait(&mutex);
//消费产品
int item;
remove_item(&item);
sleep(1);
printf("ThreadId %ld : Consumer consume product %d \n", threadid ,item);
sem_post(&mutex);
sem_post(&empty);
}
}
int main() {
//线程id
pthread_t tid[4];
//对mutex进行初始化
//第二个参数 不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享
//第三个参数 给出了信号量的初始值。
sem_init(&mutex, 0, 1);
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
in = out = 0;
//两个生产者,两个消费者
pthread_create(&tid[0], NULL ,consumer, (void*)0);
pthread_create(&tid[1], NULL ,producer, (void*)1);
pthread_create(&tid[2], NULL ,consumer, (void*)2);
pthread_create(&tid[3], NULL, producer, (void*)3);
int c=0;
while (1){
c = getchar();
//用户输入q,结束进程,否则继续运行
if (c=='q' || c=='Q'){
for (int i = 0; i < 4; ++i) {
pthread_cancel(tid[i]);
}
break;
}
}
//释放信号量
sem_destroy(&mutex);
sem_destroy(&empty);
sem_destroy(&full);
return 0;
}
2.2写者线程先到达,开始运行,之后读者线程到达,对文件资源进行P操作,写者线程进入阻塞状态,读者 线程开始运行。
vim Readerfirst.c
/*
* 读者优先
*/
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <sys/types.h>
# include <pthread.h>
# include <semaphore.h>
# include <string.h>
# include <unistd.h>
// wrt(记录型信号量) 用于实现对文件的互斥访问
// mutex 用于对count变量的互斥访问
sem_t wrt, mutex;
//记录当前有几个读进程在访问文件
int readCount;
//读者
void* Reader(void* param) {
long threadid = (long)param;
while (1){
// P操作,各进程互斥地访问 mutex
sem_wait(&mutex);
readCount++;
if(readCount == 1)
sem_wait(&wrt);
// V操作
sem_post(&mutex);
printf("Thread %ld: is reading\n", threadid);
sleep(0.3);
sem_wait(&mutex);
readCount--;
if(readCount == 0)
sem_post(&wrt);
sem_post(&mutex);
}
}
//写者
void* Writer(void* param) {
long threadid = (long)param;
while (1){
sem_wait(&wrt);
printf("Thread %ld: is writing\n", threadid);
sleep(0.5);
sem_post(&wrt);
}
}
int main() {
sem_init(&mutex, 0, 1);
sem_init(&wrt, 0, 1);
readCount = 0;
pthread_t tid[4];
//两个写者,两个读者
pthread_create(&tid[0], NULL ,Writer, (void*)0);
pthread_create(&tid[1], NULL, Writer, (void*)1);
pthread_create(&tid[2], NULL ,Reader, (void*)2);
pthread_create(&tid[3], NULL ,Reader, (void*)3);
int c=0;
while(1){
c=getchar();
//用户输入q,结束进程,否则继续运行
if (c=='q' || c=='Q'){
for (int i = 0; i < 4; ++i) {
pthread_cancel(tid[i]);
}
break;
}
}
//信号量销毁
sem_destroy(&mutex);
sem_destroy(&wrt);
return 0;
}
2.3读者线程运行时,写者线程到达,因为写者优先,所以读者进程进入阻塞状态,写者线程运
vim Writerfirst.c
/*
* 写者优先
*/
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <sys/types.h>
# include <pthread.h>
# include <semaphore.h>
# include <string.h>
# include <unistd.h>
// RWMutex 读写互斥
// mutex1 readCount互斥
// mutex2 writeCount互斥
// wrt 写者互斥
// mutex3的主要用处就是避免写者同时与多个读者进行竞争,读者中信号量RWMutex比mutex3先释放,则一旦有写者,写者可马上获得资源。
sem_t RWMutex, mutex1, mutex2, mutex3, wrt;
//用于记录正在等待的写者的数目
int writeCount, readCount;
//读者
void* Reader(void* param) {
long threadid = (long)param;
while (1){
//p操作
sem_wait(&mutex3);
sem_wait(&RWMutex);
sem_wait(&mutex2);
readCount++;
if(readCount == 1)
sem_wait(&wrt);
//v操作
sem_post(&mutex2);
sem_post(&RWMutex);
sem_post(&mutex3);
sleep(0.3);
printf("Thread %ld: is reading\n", threadid);
sem_wait(&mutex2);
readCount--;
if(readCount == 0)
sem_post(&wrt);
sem_post(&mutex2);
}
}
//写者
void* Writer(void* param) {
long threadid = (long)param;
while (1){
sem_wait(&mutex1);
writeCount++;
if(writeCount == 1){
sem_wait(&RWMutex);
}
sem_post(&mutex1);
sem_wait(&wrt);
sleep(0.5);
printf("Thread %ld: is writing\n", threadid );
sem_post(&wrt);
sem_wait(&mutex1);
writeCount--;
if(writeCount == 0) {
sem_post(&RWMutex);
}
sem_post(&mutex1);
}
}
int main() {
sem_init(&mutex1, 0, 1);
sem_init(&mutex2, 0, 1);
sem_init(&mutex3, 0, 1);
sem_init(&wrt, 0, 1);
sem_init(&RWMutex, 0, 1);
readCount = writeCount = 0;
pthread_t tid[4];
//两个写者,两个读者
pthread_create(&tid[0], NULL ,Reader, (void*)2);
pthread_create(&tid[1], NULL ,Reader, (void*)3);
pthread_create(&tid[2], NULL ,Writer, (void*)0);
pthread_create(&tid[3], NULL, Writer, (void*)1);
int c=0;
while(1){
c=getchar();
//用户输入q,结束进程,否则继续运行
if (c=='q' || c=='Q'){
for (int i = 0; i < 4; ++i) {
pthread_cancel(tid[i]);
}
break;
}
}
sem_destroy(&mutex1);
sem_destroy(&mutex2);
sem_destroy(&mutex3);
sem_destroy(&RWMutex);
sem_destroy(&wrt);
return 0;
}
2.4本程序用五个线程模拟五位哲学家,用五个sem_t型信号量模拟五根筷子(临界资源),解题的思想是:每次让每位哲学家优先拿起自己左边的筷子,并限制最多有四个人同时拿起筷子,从而避免了死锁情况,该程序模拟五个哲学家进餐一次的情况,并重复模拟了1000次以上来检验是否出现死锁情况,经检验程序运行正常,没有出现死锁状况,此处图片仅展示了第1000次的情况。
vim Philosophers.c
#include <unistd.h>
#include "pthread.h"
#include "stdio.h"
#include "stdlib.h"
#include "semaphore.h"
#define NUM 5
int ID[NUM]={0,1,2,3,4};
sem_t sem_chopsticks[NUM];
sem_t sem_eaters;
int eaters_num=0;
void sem_signal_init(){
int i;
for (i=0; i<NUM; i++){
if (sem_init(&sem_chopsticks[i],0,1) == -1){
perror("oops:em_init error!");
exit(1);
}
}
if (sem_init(&sem_eaters,0,NUM-1) == -1){
perror("oops:em_init error!");
exit(1);
}
}
void philosopher(void * ptid){
int pthread_id = *(int *)ptid%NUM;
printf("%d philosopher is thinking...\n",(int)pthread_id);
sem_wait(&sem_eaters);
sem_wait(&sem_chopsticks[pthread_id]);
printf("%d philosopher takes chopstick %d...\n",(int)pthread_id,(int)pthread_id);
sem_wait(&sem_chopsticks[(pthread_id+1)%NUM]);
printf("%d philosopher takes chopstick %d...\n",(int)pthread_id,((int)pthread_id+1)%NUM);
printf("%d philosopher is eating, %d philosopher had already dined.\n",(int)pthread_id,eaters_num);
sem_post(&sem_chopsticks[(pthread_id+1)%NUM]);
sem_post(&sem_chopsticks[pthread_id]);
sem_post(&sem_eaters);
eaters_num++;
printf("%d philosopher had dined, by now %d philosopher had already dined.\n",(int)pthread_id,eaters_num);
}
int main(){
int i,l,j,k;
for (l = 0; l < 1000; ++l) {
printf("**********************%d times try ******************************",l+1);
pthread_t philosopher_threads[NUM];
sem_signal_init();
for ( i= 0; i < NUM; i++) {
printf("%d times\n",i);
if (pthread_create(&philosopher_threads[i], NULL, (void *)&philosopher,&ID[i]) != 0){
perror("oops:pthread_create error!");
exit(1);
}
}
for ( j = 0; j < NUM; j++) {
pthread_join(philosopher_threads[j], NULL);
}
sem_destroy(&sem_eaters);
for (k = 0; k < NUM ; ++k) {
sem_destroy(&sem_chopsticks[k]);
}
eaters_num = 0;
// sleep(2);
}
return 0;
}