zoukankan      html  css  js  c++  java
  • 信息安全系统设计基础第十二周学习总结—20135227黄晓妍

    第十二章 并发编程

      操作系统提供了三种基本的构造并发程序的方法:

      1、进程。每个逻辑控制流都是一个进程,由内核来调度和维护;

      2、I/O多路复用。

      3、线程。

      

      一、基于进程的并发编程

                    

          在接受连接请求之后,服务器派生出一个子进程,这个子进程获得服务器描述表完整的拷贝。子进程关闭它的拷贝中监听描述符3,父进程关闭它的已连接描述符4的拷贝,因为不需要这些描述符了。

          程序实例:

           

        因为通常服务器会运行很长时间,所以需要一个SIGCHLD处理程序,来回收僵死进程。因为当SIGCHLD执行时,信号是阻塞的,而UNIX信号是不排队的,所以SIGCHLD必须准备好回收多个僵死进程。另外注意,循环中的父进程和子进程关闭各自需要关闭的描述符。

        进程能够共享文件表,但不共享用户地址空间。

      二、基于I/O多路复用的并发编程

        1、面对困境——服务器必须响应两个互相独立的I/O事件:1)网络客户端发起的连接请求  2)用户在键盘上键入的命令 ,解决的办法是I/O多路复用技术。基本思想是,使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

           select函数如下:

                      

         程序示例如下:

                          

         使用select函数的过程如下:

         第一步,初始化fd_set集,19~22行;

         第二步,调用select,25行;

         第三步,根据fd_set集合现在的值,判断是哪种I/O事件,26~31行。

        2、基于I/O多路复用的并发事件驱动服务器

            I/O多路复用可以用做并发事件驱动程序的基础,在事件驱动程序中,流是因为某种事件而前进的,一般概念是把逻辑流模型化为状态机。一个状态机就是一组状态、输入事件和转移。

            并发事件驱动程序中echo服务器中逻辑流的状态机,如下图所示:

                                   

      三、基于线程的并发编程

        1、线程运行在进程上下文中的逻辑流。线程由内核自动调度,每个线程都有它自己的线程上下文。

        2、线程执行模型。多线程的执行模型在某些方面和多进程的执行模型相似。每个进程开始生命周期时都是单一线程,这个线程称为主线程。在某一时刻,主线程创建一个对等线程,从在此刻开始,两个线程就并发地运行。

                                         

        3、Posix线程

             创建线程:

                       

            获取自身ID:

                        

           终止线程:

           有以下四种方式终止线程:

           当顶层的线程例程返回时,线程会隐式终止;

           线程调用pthread_exit函数,线程会显示终止;如果主线程调用pthread_exit,它会等到所有其他对等线程终止,然后再终止主线程和整个线程,返回值为thread_return;

           某个对等线程调用exut函数,则函数终止进程和所有与该进程相关的线程;

           另一个对等线程调用以当前ID为参数的函数ptherad_cancel来终止当前线程。

                           

            

          

                          

         回收已终止线程的资源:

                        

         pthread_join函数会终止,直到线程tid终止。和wait不同,该函数只能回收指定id的线程,不能回收任意线程。

         分离线程:

         一个可结合的线程能够被其他线程回收其资源和杀死,在被其他线程回收之前,它的存储其资源是没有被释放的;相反,一个分离的线程是不能被其他线程回收或杀死的。它的存储器资源是在它终止时系统自动释放的。默认情况下,线程被创建成可结合的。但现实程序中,有很好的理由要使用分离线程。

                      

         初始化线程:该函数用来初始化多个线程共享的全局变量。

                      

         一个基于线程的并发服务器:

                                           

                  

          以上程序可能会出错,因为在对等线程的赋值语句和主线程的accept的语句见引入了竞争——如果赋值语句在下一个accept之前完成,则不会出错;如果赋值语句是在accept之后完成,那么对等线程的局部变量connfd就得到下一次连接的描述符。解决办法是,必须将每个accept返回的描述符分配到它自己的动态分配的存储器块。(21~23行)  32行:动态内存空间释放,释放那个指向动态内存的指针即可,不一定非要是malloc当时生成的指针。

        4、多线程程序中的共享变量

              每个线程都有它自己独自的线程上下文,包括线程ID、栈、栈指针、程序计数器、条件码和通用目的寄存器值。每个线程和其他线程一起共享进程上下文的剩余部分。寄存器是从不共享的,而虚拟存储器总是共享的。线程化的c程序中变量根据它们的存储器类型被映射到虚拟存储器:全局变量,本地自动变量(不共享),本地静态变量。

        5、用信号量同步线程

            共享变量引入了同步错误。

            进度图: 

                                                                                             

      轨迹线示例:

                                                                       

      临界区(不安全区):

          

                      

         信号量:是用信号量解决同步问题,信号量s是具有非负整数值的全局变量,有两种特殊的操作来处理(P和V):

                    P(s):如果s非零,那么P将s减1,并且立即返回。如果s为0,那么就挂起这个线程,直到s变为非零;

                    V(s):V操作将s加1。

        使用信号量实现互斥:

                                    

          利用信号量调度共享资源:在这种场景中,一个线程用信号量操作来通知另一个线程,程序状态中的某个条件已经为真了。两个经典应用:

          a)生产者——消费者问题

                                 

          要求:必须保证对缓冲区的访问是互斥的;还需要调度对缓冲区的访问,即,如果缓冲区是满的(没有空的槽位),那么生产者必须等待直到有一个空的槽位为止,如果缓冲区是空的(即没有可取的项目),那么消费者必须等待直到有一个项目变为可用。

                                   

                

                

           注释:5~13行,缓冲区初始化,主要是对缓冲区结构体进行相关操作;16~19行,释放缓冲区存储空间;22~29行,生产(有空槽的话,在空槽中插入内容);32~4行,消费(去除某个槽中的内容,使该槽为空)

          b)读者——写者问题

            修改对象的线程叫做写者;只读对象的线程叫做读者。写着必须拥有对对象的独占访问,而读者可以和无限多个其他读者共享对象。读者——写者问题基本分为两类:第一类,读者优先,要求不要让读者等待,除非已经把使用对象的权限赋予了一个写者。换句话说,读者不会因为有一个写者等待而等待;第二类,写者优先,要求一定能写者准备好可以写,它就会尽可能地完成它的写操作。同第一类问题不同,在一个写者后到达的读者必须等待,即使这个写者也是在等待。以下程序给出了第一类读者——写者问题的解答:

                           

          注释:信号量w控制对访问共享对象的临界区的访问。信号量mutex保护对共享变量readcnt的访问,readcnt统计当前临界区的读者数量。每当一个写者进入临界区,它就对互斥锁w加锁,每当它离开临界区时,对w解锁,这就保证了任意时刻临界区最多有一个写者;另一方面,只有第一个进入临界区的读者对w加锁,而只有最后一个离开临界区的读者对w解锁。

          综合:基于预线程的并发服务器之前介绍的基于线程的并发服务器,需要为每个客户端新建一个新线程,导致不小的代价。一个基于预线程化的服务器通过使用如下图所示的生产者——消费者模型来降低这种开销。服务器是由一个主线程和一组工作组线程构成的。主线程不断地接受来自客户端的连接请求,并将得到的连接描述符放在一个有限缓冲区中。每一个工作组线程反复地从共享缓冲区中取出描述符,为客户端服务,然后等待下一个描述符。

                               

          程序示例如下图:

                                               

                    

                                                                   

                                                                   

                                                      

                      

                                                                

           注释:26~27行,产生工作组线程;29~32行,接受客户端的连接请求,并把这些描述符放到缓冲区;35~43行,每个线程所要完成的工作;19行,初始化线程共享的全局变量。初始化有两种方式,一种是它要求主线程显示地调用一个初始化函数;第二种是,在此显示的,当第一次有某个线程调用echo_cnt函数时,使用pthread_once函数去调用初始化函数。

          6、其他并发问题

               a)四种不安全函数;

               b)可重入函数。可重入函数是线程安全函数的一个真子集,它不访问任何共享数据。可重入安全函数通常比不可重入函数更有效,因为它们不需要任何同步原语。

               c)竞争。当程序员错误地假设逻辑流该如何调度时,就会发生竞争。为了消除竞争,通常我们会动态地分配内存空间。

               d)死锁。当一个流等待一个永远不会发生的事件时,就会发生死锁。

  • 相关阅读:
    得到相对Plugin的路径
    GEF常见问题4:非矩形图元
    在Eclipse的About对话框上添加自己的图标
    用GMF生成简化的数据库设计器
    全超实用的Javascript类库和jQuery插件大全之一:图片,地图和图形
    #敏捷个人# 第二批敏捷个人推广者实践团报名
    2012年最新的12款超棒jQuery插件
    《敏捷个人》周刊 第14期 (可下载)
    时间管理:敏捷个人时中法卡片
    #敏捷个人# 实践团报名
  • 原文地址:https://www.cnblogs.com/angelahxy/p/5023166.html
Copyright © 2011-2022 走看看