• Day39——互斥锁,线程技术


    互斥锁

    • 本质

      将并发变成串行 虽然牺牲了程序的执行效率但是保证了数据安全
      
      • 1
    • 作用

      多个程序同时操作一份数据的时候很容易产生数据错乱!!!
      为了避免数据错乱 我们需要使用互斥锁
      
      • 1
      • 2
    • 模拟抢票系统(合理)

      ###  db.json 自己提前创建好
       with open('db.json', 'w', encoding='utf-8') as f:
              dic={'count':1}
             json.dump(dic, f)
          
       
      ### searc方法 打印剩余票数
      def search():
          time.sleep(random.random())
          with open('db.json', encoding='utf-8') as f:
              dic = json.load(f)
              print(f'剩余票数:{dic["count"]}')
       
       
              
      ### 模拟多用户(多进程)抢票
      def get():
          with open('db.json', encoding='utf-8') as f:
              dic = json.load(f)
          time.sleep(random.randint(0, 2))
          if dic['count'] > 0:
              dic['count'] -= 1
              with open('db.json', 'w', encoding='utf-8') as f:
                  json.dump(dic, f)
              print(f'用户:{os.getpid()} ,购买成功~~')
          else:
              print(f'{os.getpid()} 没票了~~~~')
       
       
      def task(lock):
          search()        
          lock.acquire() #给抢票购买, 加锁 , 既保证了数据的安全性,也保证了数据公平性
          get()
          lock.release()# 解锁
       
       
      if __name__ == '__main__':
          lock = Lock()
          for i in range(5):
              p1 = Process(target=task, args=(lock,)) # 模拟5个用户进程
              p1.start()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
    • 关键字

      acquire: 加锁,所有人去抢,抢到的人进入,其他人等待解锁
      release: 解锁,释放

    • 强调

      互斥锁只应该出现在多个程序操作数据的地方 其他位置尽量不要加
      ps:以后我们自己处理锁的情况很少 只需要知道锁的功能即可

    线程理论

    • 本质

      线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

    • 进程与线程的理解

      进程是资源单位
      	进程相当于是车间 进程负责给内部的线程提供相应的资源()
      线程是执行单位
          线程相当于是车间里面的流水线 线程负责执行真正的功能
      
      • 1
      • 2
      • 3
      • 4
    • 特点

      1.线程是独立调度和分派的基本单位。
      2.同一进程中的多条线程将共享该进程中的全部系统资源。
      3.一个进程可以有很多线程,每条线程并行执行不同的任务。
      4.一个进程至少含有一个线程

    • 线程与进程的区别可以归纳为以下4点:

      1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的 线程在其它进程不可见。
      2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
      3)调度和切换:线程上下文切换比进程上下文切换要快得多。
      4)在多线程OS中,进程不是一个可执行的实体

    • 多进程与多线程的区别

      多进程
        需要申请内存空间 需要拷贝全部代码 资源消耗大
      多线程
        不需要申请内存空间 也不需要拷贝全部代码 资源消耗小
      
      • 1
      • 2
      • 3
      • 4

    创建线程的两种方式

    • 强调

      开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
      也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写

    • 模块导入

      from threading import Thread
      
      • 1
    • Python创建Thread对象语法如下:

      import threading
      threading.Thread(target=None, name=None,  args=())
      
      • 1
      • 2

      主要参数说明:

      1. target 是函数名字,需要调用的函数。
      2. name 设置线程名字。
      3. args 函数需要的参数,以元祖( tuple)的形式传入
    • Thread 对象主要方法说明:

      • run(): 用以表示线程活动的方法。
      • start():启动线程活动。
      • join(): 等待至线程中止。
      • isAlive(): 返回线程是否活动的
      • getName(): 返回线程名。
      • setName(): 设置线程名。
    • 方法一

      from threading import Thread
      import time
      
      def task(name):
          print(f'{name}正在运行')
          time.sleep(3)
          print(f'{name}运行结束')
      
      
      # t = Thread(target=task, args=('jason',))
      # t.start()
      # print('主线程')
      
      if __name__ == '__main__':  # 用不用main方法都行
          t = Thread(target=task, args=('jason',))
          t.start()
          print('主线程')
      
      '''
      输出结果:
      jason正在运行
      主线程
      jason运行结束
      '''
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • 方法二

      from threading import Thread
      import time
      
      
      class MyThread(Thread):
          def __init__(self, name):
              super().__init__()
              self.name = name
      
          def run(self):
              print(f'{self.name}正在运行')
              time.sleep(3)
              print(f'{self.name}运行结束')
      
      
      obj = MyThread('jason')
      obj.start()
      print('主线程')
      
      '''
      输出结果:
      jason正在运行
      主线程
      jason运行结束
      '''
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

    join方法

    from threading import Thread
    import time
    
    
    def task(name):
        print(f'{name}正在运行')
        time.sleep(3)
        print(f'{name}运行结束')
    
    
    t = Thread(target=task, args=('jason', ))
    t.start()
    t.join()  # 主线程代码等待子线程代码运行完毕之后再往下执行,和进程的join方法作用一致
    print('主线程')
    
    '''
    输出结果:
    jason正在运行
    jason运行结束
    主线程
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    同一个进程下线程间数据共享

    from threading import Thread
    
    money = 1000
    
    
    def func():
        global money
        money = 666
    
    
    t = Thread(target=func)
    t.start()
    t.join()  # 确保线程运行完毕 再查找money 结果更具有说服性
    print(money)
    
    '''
    输出结果:
    666
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    线程对象相关方法

    Python的threading模块有个current_thread()函数,它将返回当前线程的示例。从当前线程的示例可以获得前运行线程名字。

    from threading import Thread, current_thread
    current_thread().name
    
    • 1
    • 2

    强调:同一个进程下开设的多个线程拥有相同的进程号

    统计进程下的线程数:active_count()

    守护线程

    • 本质

      在多线程开发中,如果子线程设定为了守护线程,守护线程会等待主线程运行完毕后被销毁。一个主线程可以设置多个守护线程,守护线程运行的前提是,主线程必须存在,如果主线程不存在了,守护线程会被销毁。

    • 代码实操

      from threading import Thread
      import time
      
      def task():
          print('子线程运行task函数')
          time.sleep(3)
          print('子线程运行task结束')
      
      
      t = Thread(target=task)
      t.daemon = True
      t.start()
      # t.daemon = True  # 会报错
      print('主线程')
      
      '''
      输出结果:
      子线程运行task函数
      主线程
      '''
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    强调:进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!

    GIL全局解释器锁

    • 什么是 GIL 呢?

      GIL 是最流程的 CPython 解释器(平常称为 Python)中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。
      
      • 1
    • GIL 的功能

      在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
      
      • 1
    • 储备知识

      python解释器也是由编程语言写出来的

      Cpython 用C写出来的
      Jpython 用Java写出来的
      Pypython 用python写出来的

      ps:最常用的就是Cpython(默认)

    • 官方文档

      In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

      翻译:

      在 CPython 解释器中,全局解释锁 GIL 是在于执行 Python 字节码时,为了保护访问 Python 对象 而阻止多个线程执行的一把互斥锁。这把锁的存在主要是因为 CPython 解释器的内存管理不是线程安全的。然而直到今天 GIL 依旧存在,现在的很多功能已经习惯于依赖它作为执行的保证。

      总结:

      1.GIL的研究是Cpython解释器的特点 不是python语言的特点
      2.GIL本质也是一把互斥锁
      3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
      言外之意:单进程下的多线程无法利用多核优势 效率低!!!
      4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的

      GIL 工作流程示意图:
      GIL 工作流程示意图

    • 问题引出:

      1.误解:python的多线程就是垃圾 利用不到多核优势

      python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
      
      • 1

      2.误解:既然有GIL 那么以后我们写代码都不需要加互斥锁

      不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
      针对程序中自己的数据应该自己加锁处理
      
      • 1
      • 2

    总结:

    以后用python就可以多进程下面开设多线程从而达到效率最大化

    1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势

    2.GIL在实际编程中其实不用考虑

  • 相关阅读:
    第六章 将对象映射到 XML - 控制对象值属性的映射形式
    【Docker】uwsgi的测试test.py显示hello-world -20220802
    思科C9300交换机Bundle模式转换为Install模式
    20220701 Barbalat引理证明
    烟台个人开发者申请软件著作权的好处
    SynthText流程解读 - 不看代码不知道的那些事
    基于Java+SpringBoot+Thymeleaf+Mysql校园运动场地预约系统设计与实现
    java.lang.Float类下parseFloat(String s)方法具有什么功能呢?
    Ansible自动化运维工具之playbook剧本编写(上)
    WINDOWS与LINUX的文件文件共享
  • 原文地址:https://blog.csdn.net/lzq78998/article/details/126273631