zoukankan      html  css  js  c++  java
  • 并发编程 之 线程 之 Thread 模块(数据共享, 守护线程), 锁Lock(递归锁), 信号量, 事件, 条件, 定时器 (二)

    Thread 模块 from threading import Thread:

    与进程的效率差别:

    import time
    from threading import Thread
    from multiprocessing import Process
    # 效率差别
    def func(a):
        a = a + 1
    
    if __name__ == '__main__':
        start = time.time()
        t_l = []
        for i in range(50):
            t = Thread(target=func,args=(i,))
            t.start()
            t_l.append(t)
        # t_l 50个线程对象
        for t in t_l : t.join()
        print('主线程')
        print(time.time() - start)
    
        start = time.time()
        t_l = []
        for i in range(50):
            t = Process(target=func, args=(i,))
            t.start()
            t_l.append(t)
        # t_l 50个线程对象
        for t in t_l: t.join()
        print('主进程')
        print(time.time() - start)
    
    
    
    '''
    跑一下后, 所显示的 时间差, 会非常明显的显出来
    
    之所以会这样, 是因为 
          对于多进程来说:
                需要打开多个进程, 每个进程和进程之间都是独立的空间互不干扰, 所以在打开和关闭的时候 会消耗大量的时间, 
          对于 多线程来说:
                相当于只打开了一个进程, 在一个进程中实现了更多的操作, 这样可以减少 打开和销毁 进程的一部分时间, 不过, Cpython解释器里面的线程实现不了 并行, 对于抢占CPU的利用率来说 是一个很low的东西
    '''        
    
    效率差别

    数据共享:
        在进程中不存在数据共享, 如果想要共享数据,需要引用一些其他模块

        在线程中, 数据是共享的

      守护线程:

        会等待主线程结束之后才结束, 主线程会等待所有子线程结束而结束
        主线程结束, 整个进程结束, 所以 主线程需要等待所有线程结束后才能结束
        setDaemon(True) 开启守护线程.

    import time
    from threading import Thread
    def thread1():
        while True:
            print(True)
            time.sleep(0.5)
    
    def thread2():
        print('in t2 start')
        time.sleep(3)
        print('in t2 end')
    
    if __name__ == '__main__':
        t1 = Thread(target=thread1)
        t1.setDaemon(True)
        t1.start()
        t2 = Thread(target=thread2)
        t2.start()
        time.sleep(1)
        print('主线程')
    
    
    # 主线程如果结束了 那么整个进程就结束
    # 守护线程 会等待主线程结束之后才结束.
        # 主进程 等待 守护进程 子进程
        # 守护进程 只守护主进程的代码就可以了
        # 守护线程不行 主线程如果结束了 那么整个进程就结束 所有的线程就都结束
    
    守护线程

    用类开启线程:

        继承Thread类, 在里面重写run()

    from threading import Thread,get_ident
    
    # threading.get_ident() 此方法可以用来查看线程 id
    class MyThread(Thread):
    
        def run(self):
            print('in my thread : ',get_ident(),self.args)
    
    print('main',get_ident())
    t = MyThread('wahaha')
    t.start()
    
    用类开启线程 (不含参数)

    若 方法中含有参数,  只需要在自己写的类中,重写 __init__() 构造方法, 同时继承父类的__init__() 方法:

    from threading import Thread,get_ident
    # 开启线程的第二种方式和查看线程id
    class MyThread(Thread):
        def __init__(self,args):
            super().__init__()   # Thread类的init,在这个方法中做了很多对self的赋值操作,都是给创建线程或者使用线程的时候用的
            self.args = args
    
        def run(self):
            print('in my thread : ',get_ident(),self.args)
    
    print('main',get_ident())
    t = MyThread('wahaha')
    t.start()
    
    用类开启线程 (含参数)

    线程中的其他方法:
        is_alive() 判断是否活着
        enumerate() 将正在进行的线程组成一个列表进行返回
        activeCount() 计算正在运行的线程的数量

    锁Lock, 递归锁PLack

      在进程和线程中, 同时访问一个数据的时候就会产生数据不安全的现象   

      GIL 全局解释器锁:
        此锁是用来锁住解释器往CPU中传输需要计算的内容.

    Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
      对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
    
      在多线程环境中,Python 虚拟机按以下方式执行:
    
      a、设置 GIL;
    
      b、切换到一个线程去运行;
    
      c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
    
      d、把线程设置为睡眠状态;
    
      e、解锁 GIL;
    
      d、再次重复以上所有步骤。
      在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
    
    GIL详解

     互斥锁: Lock
        此锁是用来对内存进行锁定, 避免出现多线程 同时对某一数据进行增删改查,

        用法和进程中的锁大概也一样, 只不过在进程中的 数据本身是不被共享的, 而这里的数据本身就是共享的, 所以往往在线程中出现的概率更大.

    # 尽量不要设置全局变量
    # 只要在多线程/进程之间用到全局变量 就加上锁
    
    from threading import Lock,Thread
    lock = Lock()
    lock.acquire()
    lock.acquire()
    noodle = 100
    def func(name,lock):
        global noodle
        lock.acquire()
        noodle -= 1
        lock.release()
        print('%s吃到面了'%name)
    
    if __name__ == '__main__':
        lock = Lock()  # 线程锁 互斥锁
        t_lst = []
        for i in range(10):
            t = Thread(target=func,args=(i,lock))
            t.start()
            t_lst.append(t)
        for t in t_lst:
            t.join()
        print(noodle)
    
    Lock

    递归锁: PLock
        此锁是用来解决, 在使用互斥锁的时候出现的死锁的状况

      死锁现象: 本质上还是代码的逻辑出现了问题,
        所以 递归锁只是用来进行修补的强力胶, 而不是一个正常代码中的逻辑方法

    ps:
        死锁:
          多把锁同时应用在多个线程中

        互斥锁和递归锁哪个好:

          递归锁 快速恢复服务
          死锁问题的出现 是程序的设计或者逻辑的问题
          还应该进一步的排除和重构逻辑来保证使用互斥锁也不会发生死锁

        互斥锁和递归锁的区别:
          互斥锁 就是在一个线程中不能连续多次ACQUIRE
          递归锁 可以在同一个线程中acquire任意次,注意acquire多少次就需要release多少次

    信号量 Semphore:
      池与信号量的区别:


         效率高:
          池子里有几个进程/线程 一共就几个进程/线程
          不管有多少任务, 池子的个数都是固定的
          开启进程和关闭进程这些事都是需要固定的时间开销
          不产生额外的时间开销
          且进程池中的进程数控制的好, 那么操作系统的压力越小

        信号量:
          有多少个任务就开多少 进程/线程
          可以帮助你进啊少操作系统切换的负担
          但是并不能帮助你减少 进程/线程的开启和关闭的时间,
          所以 信号量的效率略低

    事件 Even: 同进程的事件整体来说是一样的.
      方法:
        wait() 等待, 
        set()
        clear()
        is_set()

    条件:Condition
      方法:
        acquire()
        release() 
        wait() 阻塞
        notify() 让wait接触阻塞

      ps: wait() 和 notify() 必须在执行这两个方法的前后加上 acquire()和release()

    定时器 Timer:

      定时器,指定n秒后执行某个操作

    from threading import Timer
     
    def hello():
        print("hello, world")
     
    t = Timer(1, hello)
    t.start()  # after 1 seconds, "hello, world" will be printed
  • 相关阅读:
    Linux 资源监控整体分析-TOP
    GO基础之闭包
    GO基础之函数的高级用法
    GO基础之函数
    GO基础之流程控制语句
    数据结构导论(第二章线性表)
    JVM 参数配置
    GO基础之变量的使用
    数据结构导论(第一章概论)
    网络经济与企业管理(第11章:企业文化管理)
  • 原文地址:https://www.cnblogs.com/wr13640959765/p/9392873.html
Copyright © 2011-2022 走看看