zoukankan      html  css  js  c++  java
  • DAY 27 PYTHON入门

    一、IPC机制
    IPC:
    进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。
    进程是计算机系统分配资源的最小单位(严格说来是线程)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。通常,使用进程间通信的两个应用可以被分为客户端和服务器(见主从式架构),客户端进程请求数据,服务端响应客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。
    IPC对微内核和nano内核的设计过程非常重要。 微内核减少了内核提供的功能数量。 然后通过IPC与服务器通信获得这些功能,与普通的宏内核相比,IPC的数量大幅增加。


    二、生产者消费者模型
    在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

    为什么要使用生产者和消费者模式
    在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

    什么是生产者消费者模式
    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
    基于队列实现生产者消费者模型

    #生产者消费者模型总结

    #程序中有两类角色
    一类负责生产数据(生产者)
    一类负责处理数据(消费者)

    #引入生产者消费者模型为了解决的问题是:
    平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度

    #如何实现:
    生产者<-->队列<——>消费者
    #生产者消费者模型实现类程序的解耦和

    三、线程理论
    1.线程和多线程定义
    线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

    2.线程和进程的区别
    1.进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
    2.进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
    3.与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
    4.线程是进程中的一个单元,他是不能独立运行的,他必须依赖某个进程。

    3.使用多线程的原因
    ·使用线程可以把占据时间长的程序中的任务放到后台去处理
    ·用户界面的更新和交互是不能被阻塞的,耗时操作是不能放在主线程的
    ·当CPU是多核的时候,不同的进程可以实现并行,这样就提高了程序的运行效率
    ·有些任务并不是连续的,需要等待,比如网络收发数据、用户输入等, 这些操作并不需要等到执行完成,二是需要有变化才操作。

    4 多线程使用注意事项
    4.1 开辟过多线程的问题
    ·系统需要为线程开辟存储空间来管理他,线程过多,会占用大量内存。
    ·线程是通过CPU来调度的,过多的线程会加大CPU的负担,会增加耗电量,还会让机器发烫。

    4.2 其他问题
    ·多个线程同时访问同一数据的访问,要确保数据的安全
    ·线程死锁

    5 线程的生命周期
    1.线程创建后放到等待队列中,等待CPU的调度,所以新建只有到就绪一种转换。
    2.就绪的是在等待的过程中,没有被CPU执行,不会和其他的线程争夺资源,所以会会变成阻塞状态,只会在运行的状态里切换。
    3.运行的线程可以变成就绪、阻塞或者死亡的状态,当时间片到后,线程会进入到就绪状态,当某个线程得不到资源,会被阻塞,当线程执行完成后,线程会死亡。
    4.线程死亡后,不会保留线程相关信息,会释放掉占用的内存,所以死亡的线程是无法逆转的。就不可以转成其他几种状态。

    6 线程间的通讯
    线程间的通信机制实现起来则相对简单,主要包括互斥锁、条件变量、读写锁和线程信号等。
    6.1 互斥锁
    互斥锁基本原理:互斥锁以排他的方式防止数据被并发修改。当多个线程共享相同的内存时,需要确保每个线程看到的数据是一样的。如果是只读,那么一定是一样的。如果是可读可写,在一个线程操作一个内存区域时,包含三个步骤,即读出数据,修改数据,写回数据。如果该线程在没有写回数据前,另一个线程来访问同一个区域,如果是读,得不到最新的数据状态,如果是写,则会造成数据覆盖等问题。[12306购票]
    互斥锁就两个状态:开锁(0),上锁(1)。将某个共享资源和互斥锁绑定后,对该共享资源的访问操作如下:
    A】在访问资源前,首先申请该互斥锁,如果在开锁状态,则申请到该锁对象,并立即占有该锁(锁定)。以防其他线程访问修改此资源。如果该锁处于锁定状态,默认阻塞等待。
    B】原则上只有锁定该互斥锁的进程才能释放此互斥锁

    6.2 条件变量
    条件变量通信机制:
    1、条件变量基本原理:条件变量的出现,可以弥补互斥锁的缺陷,有些问题仅仅靠互斥锁无法解决。但是条件变量不能单独使用,必须配合互斥锁一起实现对资源的互斥访问

    6.3 读写锁
    1、读写锁基本原理:在对数据的读写操作时,往往是读占主要部分。为了满足当前能够允许多个读出,但只允许一个写入的需求。线程提供了读写锁来实现。读写锁基本原则如下:
    A】如果有其他线程读数据,则允许其他线程执行读操作,但是不允许写操作。
    B】如果有其他线程申请了写锁,则其他线程既不能申请读锁,也不能申请写锁

    6.4 线程信号:
    线程是一种轻量级的进程,因此进程的信号同样适用于线程。不过相对于进程信号,线程拥有与信号相关的私有数据——线程信号掩码,则就决定了线程在信号操作时具有以下特性:
    A】每个线程可以先别的线程发送信号,pthread_kill()函数用来完成这一操作。
    B】每个线程都可以设置自己的阻塞集合。pthread_sigmask()用来完成这一操作。类似于进程中的sigprocmask()函数。主进程创建出来的线程继承主进程的掩码。
    C】每个线程需要设置针对某信号的处理方式,但同一个进程中对某信号的处理方式只能有一个有效,即最后一次设置的处理方式。即在所有的线程里,同一信号在任何线程里的对该信号的处理一定相同
    D】如果别的进程向当前进程发来一个信号,具体由哪个线程去处理,是未知的。

    四、开启线程的两种方式
    线程实现的两种方式
    继承(extends)Thread
    一个类A通过继承Thread,然后覆盖重写里面的run()方法,在main方法中创建对象a,通过对象调用a.start()的方式来启动线程

    实现接口(implements) Runnable
    一个类B实现Runnable的接口,然后也需要覆盖重写里面的run()方法,但是和继承不同的是,接口中的Runnable里面没有.start()方法,所以在main创造对象b时,还需要创建Thread的一个对象,把b这个对象传入到Thread里面。这样才能调用start()方法.

    五、线程对象的相关属性与方法
    Python的线程开发使用标准库threading
    进程靠线程执行代码,至少有一个主线程,其它线程是工作线程。
    主线程是第一个启动的线程。
    父线程:如果线程A中启动了一个线程B,A就是B的父线程。
    子线程:B就是A的子线程。

    过threading.Thread创建一个线程对象,target是目标函数,可以使用name为线程指定名称。
    启动线程需要调用线程的start()方法才能启动
    线程之所以执行函数,是因为线程中就是要执行代码的,而简单的封装就是函数,所以还是函数调用
    线程执行完,线程就退出了
    Python没有提供线程退出的方法,线程在下面情况时退出
    线程函数内语句执行完毕
    线程函数中抛出未处理的异常
    Python的线程没有优先级、没有线程组的概念,也不能被销毁、停止、挂起,那也就没有恢复、中断了。

    threading的属性和方法
    # active_count,enumerate方法返回的值包含主线程。
    import threading

    def shothread():
    print("- "*30)
    print(threading.enumerate())
    print(threading.active_count())
    print(threading.get_ident())
    print(threading.current_thread(),threading.current_thread().ident)
    print(threading.main_thread())

    shothread()
    t = threading.Thread(target=shothread)
    t.start()

    Thread线程的实例对象的属性和方法
    Thread.name:线程的名字,一个标识符,线程的名称可以重名。getName(),setName()获取、设置这个名词

    Thread.ident:线程ID,是个非0的整数。线程启动后才会有ID,否则为None。线程退出,此时Id依旧可以访问。此ID会被系统重复使用

    Thread.is_alive():返回线程是否或者

    注意:线程的name这是一个名称,可以重复;ID必须唯一,但可以在线程退出后再利用。

    import threading
    import time

    def worker():
    for i in range(5):
    time.sleep(1)
    print("i am working")
    print("finished")

    t = threading.Thread(target=worker,name="worker1")
    print(t.name,t.ident,t.is_alive())
    t.start()

    while True:
    time.sleep(0.8)
    tbool = t.is_alive()
    if tbool:
    print("main print id = {},name = {},是否活着{}".format(t.ident,t.name,tbool))
    else:
    print(t.ident,t.is_alive())
    t.start() #不能重新启动,线程只能启动一次


    六、互斥锁
    互斥锁(Mutex)
    1、互斥锁的本质:
    首先需要明确一点,互斥锁实际上是一种变量,在使用互斥锁时,实际上是对这个变量进行置0置1操作并进行判断使得线程能够获得锁或释放锁。
    (互斥锁的具体实现在文末讲解)
    2、作用:互斥锁的作用是对临界区加以保护,以使任意时刻只有一个线程能够执行临界区的代码。实现了多线程之间的互斥。
    3、接口:
    使用互斥锁主要有以下几个接口操作

    //两种方法对锁进行初始化
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    int pthread_mutex_init(pthread_mutex_t *restrict mutex,
    const pthread_mutexattr_t *restrict attr);
    //互斥锁的销毁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    //获得锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);

    //释放锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    两种获得锁方法的比较:
    pthread_mutex_lock:如果此时已经有另一个线程已经获得了锁,那么当前线程调用该函数后就会被挂起等待,直到有另一个线程释放了锁,该线程会被唤醒。
    pthread_mutex_trylock:如果此时有另一个贤臣已经获得了锁,那么当前线程调用该函数后会立即返回并返回设置出错码为EBUSY,即它不会使当前线程挂起等待。

    既然已经有了互斥锁,我们可以对上述代码进行修改,如下:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    static int g_val=0;
    void* pthread_mem(void* arg)
    {
    int i=0;
    int val=0;
    while(i<500000)
    {
    pthread_mutex_lock(&mutex);
    val = g_val;
    i++;
    g_val=val+1;
    pthread_mutex_unlock(&mutex);
    }
    return NULL;
    }

    这段代码通过对临界区进行加锁与解锁,每个线程在进入临界区的时候获得一把锁,操作执行完成后释放锁资源,使得其他等待的线程能够抱锁进入,这样就确保了每个线程进来执行的操作都是原子的,这样使得最后的结果为1000000.

    互斥锁的底层实现:
    上边已经提到,mutex的本质是一种变量。假设mutex为1时表示锁是空闲的,此时某个进程如果调用lock函数就可以获得所资源;当mutex为0时表示锁被其他进程占用,如果此时有进程调用lock来获得锁时会被挂起等待。

    1、lock和unlock的实现方案一
    unlock:这个操作是原子的,即通过执行unlock的代码后,mutex要么为1,要么不为1
    lock:执行lock时,先要对mutex进行判断,如果mutex>0,修改mutex=0,否则就表示锁被占用,将当前进程挂起等待。假设mutex为1,且有两个线程A和B来进行lock以获得锁,对于A和B来说,他两都拿到mutex为1,都会进入if()条件内部,此时线程A已经将锁拿到(mutex置为0),而B线程并不知道,也将mutex置为0,因此,线程A和线程B都会认为自己已经获得了锁。
    对于这种方案,因为lock的过程不是原子的,也会产生错误。

    2、lock和unlock的实现方案二

    使用swap或exchange指令,这个指令的含义是将寄存器和内存单元中的数据进行交换,这条指令保证了操作的原子性:
    lock:这一步先将0赋值到寄存器al,再将mutex与al中的值交换,再进行判断。当对某个线程进行lock操作时,即使在中间任意一步被切出去也没有问题。这样就保证了lock的操作也是原子的。

    使用互斥锁引入的问题:
    使用互斥锁可能会导致死锁问题。

    死锁:
    指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。通俗一点来讲,假设A线程持有锁a,B线程持有锁b,而线程访问临界区的条件时同时具有锁a和锁b,那么A就会等待B释放锁b,B会等待A释放锁a,如果没有一种措施,他两会一直等待,这样就产生了死锁。
    死锁产生的情况
    1、系统资源不足:如果系统资源足够,每个申请锁的线程都能后获得锁,那么产生死锁的情况就会大大降低;
    2、申请锁的顺序不当:当两个线程按照不同的顺序申请、释放锁资源时也会产生死锁。( 自行执行以下代码观察现象。)

    死锁产生的条件
    1、互斥属性:即每次只能有一个线程占用资源。
    2、请求与保持:即已经申请到锁资源的线程可以继续申请。在这种情况下,一个线程也可以产生死锁情况,即抱着锁找锁。
    3、不可剥夺:线程已经得到所资源,在没有自己主动释放之前,不能被强行剥夺。
    4、循环等待:多个线程形成环路等待,每个线程都在等待相邻线程的锁资源。

    死锁的避免:
    1、既然死锁的产生是由于使用了锁,那么在能不使用锁的情况下就尽量不使用,如果有多种方案都能实现,那么尽量不选用带锁的这种方案
    2、尽量避免同时获得多把锁,如果有必要,就要保证获得锁的顺序相同

  • 相关阅读:
    开源类库项目构想,欢迎各位高手拍砖~~
    【算法10】在升序数组中查找和等于给定值的两个数
    【算法05】左旋转字符串
    【算法04】判断扑克牌中的顺子
    【算法12】时间为O(n)排序——计数排序
    【算法06】顺时针打印矩阵
    【算法08】数对之差的最大值
    【算法09】整数的转换成2进制有多少个1
    【算法03】n个骰子的总和
    【算法07】求子数组的最大和
  • 原文地址:https://www.cnblogs.com/DEJAVU888/p/14308995.html
Copyright © 2011-2022 走看看