操作系统实验3 经典同步互斥问题

2017-4-21

实验目的

通过编写程序熟悉并发,同步/互斥,死锁等概念

实验内容

[基本要求]

编写Linux环境下C程序,实现3个经典的同步/互斥问题

[具体要求]

·生产者/消费者问题

·读者/写者问题

·哲学家问题

·代码有注释,提交实验报告和源代码

[进一步要求]

·多个生产者和消费者

·多个读者和写者,考虑读者优先或是写者优先

·用区别于示例代码的思想实现哲学家问题

·可视化展示输出结果

3.实验报告

(1)生产者/消费者问题

问题思路:问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据。

解决方案:

①使用linux中的pthread库进行多线程编程,其中使用pthread_create()创建新线程,pthread_join()来等待线程结束。

②使用信号量进行多线程的同步操作。

进一步说明:信号量包括两个操作原语:sam_wait() sam_post(),在Linux中使用sem_t表示信号量,先使用sem_init初始化,然后使用sem_wait进行申请,使用sem_post进行释放。

创造多个线程: 用pthread_t定义两个线程数组,用for循环语句控制,来创建多个生产者/消费者线程。

通过信号量可以对共享资源进行保护。

把信号量的值初始化成共享资源的数量,当要使用共享资源时,可以使用wait操作进行申请,只有当有剩余资源的时候才能够申请成功,否则要进行等待。而当使用完资源时,需要使用post操作进行释放,使资源的数值加一,如果释放时有人在等待资源,则可将其唤醒。

(2)读者/写者问题

问题描述:计算机系统中的数据(文件、记录)常被多个进程共享,但其中某些进程可能只要求读数据(称为读者Reader);另一些进程则要求修改数据(称为写者Writer)。就共享数据而言,Reader和Writer是两组并发进程共享一组数据区,要求:

①允许多个读者同时执行读操作;

②不允许读者、写者同时操作;

③不允许多个写者同时操作。

Reader和Writer的同步问题分为读者优先和写者优先。

问题思路:对于读者优先:如果当前情况下没有写者,就执行读操作。【且如果之后同时存在读者和写者进程,优先执行读】如果有写者,先将当前写操作执行完毕,然后执行读。

对于写者优先 :如果当前情况下没有读者,就执行写操作。【且如果之后同时存在读者和写者进程,优先执行写】如果有读者,先将当前读操作执行完毕,然后执行写。

解决方案:①使用信号量进行多线程的同步操作。②使用readcount 和writecount计算当前线程数,并判断在执行之前是否存在读/写操作

进一步说明:两个优先的实现方式不一样 。

(3)哲学家就餐问题



哲学家就餐问题

问题描述:一张桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗面。(如图)哲学家们只做两件事情:思考和进餐,哲学家在思考时,并不影响他人。当哲学家饥饿的时候,会拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

问题思路:

5名哲学家与左右邻居对其中间筷子的访问是互斥关系。

可能产生死锁,每个哲学家都拿着左手的筷子,永远都在等右边的筷子(或者相反)。

可能产生饥饿:有可能存在一个哲学家一直没拿到筷子

关键是如何让一个哲学家拿到左右两个筷子而不造成死锁或者饥饿现象。那么解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或者死锁现象的发生。

解决方案:设置一个服务员,一次只允许最多四个哲学家进入餐厅,保证至少有一个哲学家可以进餐,消除死锁问题。未能进入餐厅的哲学家进入等待队列中,根据FIFO原则,不会产生饥饿问题。

进一步说明:在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。

哲学家就餐问题是在计算机科学中的一个经典问题,用来演示在并行计算中多线程同步时产生的问题。

4.遇到的问题

1).产生的线程不会结束:即mian函数无法结束,pthread_join仿佛是一个摆设.

解决方案:

查找pthread函数库,了解结束线程的函数有如下两种:

pthread_join:函数pthread_join用来等待一个线程的结束。主线程中要是加了这段代码,就会在加代码的位置卡主,直到这个线程执行完毕才往下走。

函数原型为:

extern int pthread_join __P ((pthread_t __th, void **__thread_return));

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。

pthread_exit:用于强制退出一个线程(非执行完毕退出),一般用于线程内部。函数原型为:

extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return.

因而只要将二者结合使用即可:

用pthread_exit在线程内退出,然后返回一个值。这个时候就跳到主线程的pthread_join,这个返回值会直接送到pthread_join,实现了主与分线程的通信。

P.S.当然也可以设置while循环,计算执行时间,到达某个自定义的时间之后,强制退出,但是问题在于循环在哪里设置?create之后线程将执行对应的函数,此时不再受主函数的控制。

2)创建线程时,无法对线程所进入的函数输入参数

解决方案:查找pthread_create函数如下:

定义:

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,

(void*)(*start_rtn)(void*),void *arg);

返回值:若成功则返回0,否则返回错误编号

pthread_t *tidp,由tidp指向的内存单元被设置为新创建线程的线程ID。

const pthread_attr_t *attr,第二个参数用来设置线程属性。(一般设NULL)

(void*)(*start_rtn)(void*),第三个参数是一个函数指针,即线程运行函数的起始地址。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

void *arg,最后一个参数是运行函数的参数。(一般设NULL)

从定义可以看到,传入的函数参数需要为void*类型,必须强制转成对应的指针才能用。

可以传参如下:pthread_create(&tpid,NULL,philosopher,(void *)&j);

3)读写者是一个一个交替进行,看不出多线程

解决方案:读写进程的悬挂时间(延迟时间)没有设置好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值