• 线程2....GIL


    1.GIL全局解释器锁(******) 
      GIL:全局解释器global interpreter lock,是一个互斥锁,存在于Cpython解释器内:
        保证线程的安全(以牺牲效率来换取数据的安全)
        阻止同一个进程内多个线程同时执行(不能并行但是能够实现并发)

        线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行
          同一进程下的多个线程在同一时刻只能有一个线程被执行,
          保证了同一个进程下多个线程之间的安全
    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.)
    PIC定义
    
    
    GIL全局解释器存在的原因:Cpython解释器的内存管理不是线程安全的

    Cpython解释器的内存管理:垃圾回收机制    

    1)引用计数    

    2)标记清除    

    3)分代回收  

    垃圾回收机制也是一个任务,和普通的代码不是串行运行,如果是串行会明显有卡顿
    垃圾回收到底是开进程还是开线程?肯定是线程,所以想运行也必须要拿到python解释器
    假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!
    也就意味着在Cpython解释器上有一把GIL全局解释器锁   

    所以在Cpython解释器中,同一个进程下:

      多个线程不能实现并行但是能够实现并发

      多个进程下的线程能够实现并行 

     

    问题:python多线程是不是就没有用了呢?

     1)四个任务:计算密集的任务,每个任务耗时10s

    单核情况下:    

      多线程好一点,消耗的资源少一点

    多核情况下:    

      开四个进程:10s多一点    

      开四个线程:40s多一点

    from multiprocessing import Process
    from threading import Thread
    import os, time
    
    
    def work():
        res = 0
        for i in range(100000000):
            res *= i
    
    
    if __name__ == '__main__':
        l = []
        print(os.cpu_count())  # 本机为8核
        start = time.time()
        for i in range(8):
            p=Process(target=work) # 耗时9.252728939056396
            p = Thread(target=work)  # 耗时35.369622230529785
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print('run time is %s' % (stop - start))
    计算密集型

    2)四个任务:IO密集的任务,每个任务io 10s

    单核情况下:    

      多线程好一点

    多核情况下:    

      多线程好一点

    from multiprocessing import Process
    from threading import Thread
    import os, time
    
    
    def work():
        time.sleep(2)
    
    
    if __name__ == '__main__':
        l = []
        print(os.cpu_count())  # 本机为8核
        start = time.time()
        for i in range(600):
            p = Process(target=work)  # 耗时4.699530839920044
            # p=Thread(target=work) # 耗时2.054128885269165
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop = time.time()
        print('run time is %s' % (stop - start))
    IO密集型

      >>> 多线程和多进程都有自己的优点,要根据项目需求合理选择 


    2.GIL全局解释器锁与普通的互斥锁的区别
      1)GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
      2)对于不同的数据,要想保证安全,需要加不同的锁
    from threading import Thread, Lock
    import time
    
    mutex = Lock()
    
    n = 100
    
    
    def task():
        global n
        mutex.acquire()
        tmp = n
        time.sleep(0.1)
        n = tmp - 1
        mutex.release()
    
    
    t_list = []
    for i in range(100):
        t = Thread(target=task)
        t.start()
        t_list.append(t)
    
    for t in t_list:
        t.join()
    
    print(n)

    3.死锁与递归锁(了解)
      自定义锁:一次acquire必须对应一次release,不能连续acquire
      递归锁:可以连续的acquire,每acquire一次计数加一(针对的是抢到锁的人)
    from threading import Thread,Lock,RLock
    import time
    
    # mutexA = Lock()
    # mutexB = Lock() # 形成死锁
    mutexA = mutexB = RLock()  # 抢锁一次计数加一,针对的是第一个抢到锁的人
    
    
    class MyThead(Thread):
        def run(self):
            self.func1()
            self.func2()
    
        def func1(self):
            mutexA.acquire()
            print('%s 抢到A锁了' % self.name)
            mutexB.acquire()
            print('%s 抢到B锁了' % self.name)
            mutexB.release()
            print('%s 释放了B锁' % self.name)
            mutexA.release()
            print('%s 释放了A锁' % self.name)
    
        def func2(self):
            mutexB.acquire()
            print('%s 抢到了B锁' % self.name)
            time.sleep(1)
            mutexA.acquire()
            print('%s 抢到A锁了' % self.name)
            mutexA.release()
            print('%s 释放了A锁' % self.name)
            mutexB.release()
            print('%s 释放了B锁' % self.name)
    
    
    for i in range(100):
        t = MyThead()
        t.start()

    4.信号量(了解)
      互斥锁:比喻为独立卫生间,所有人抢一把锁
      信号量:比喻为有多个隔间的卫生间,所有人抢多把锁
    from threading import Thread, Semaphore
    import time
    import random
    
    sm = Semaphore(5)  # 五个厕所五把锁
    
    
    def task(name):
        sm.acquire()
        print('%s正在蹲坑' % name)
        # 模拟蹲坑耗时
        time.sleep(random.randint(1, 5))
        sm.release()
    
    
    if __name__ == '__main__':
        for i in range(20):
            t = Thread(target=task, args=('伞兵%s号' % i,))
            t.start()

    5.event事件
      一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
    from threading import Event, Thread
    import time
    
    event = Event()
    
    
    def light():
        print('红灯亮着!')
        time.sleep(3)
        event.set()  # 解除阻塞,给另一个子线程发送信号
        print('绿灯亮了!')
    
    
    def car(i):
        print('%s 正在等红灯了' % i)
        event.wait()  # 阻塞,等待接收set()信号
        print('%s 加油门飙车了' % i)
    
    
    t1 = Thread(target=light)
    t1.start()
    
    for i in range(10):
        t = Thread(target=car, args=(i,))
        t.start()

    6.线程queue
    同一个进程下的线程数据都是共享的为什么还要用queue?
      queue本身自带锁的功能,能够保证数据的安全

      1)Queue 队列:先进先出
      2)LifoQueue 堆栈:先进后出 last in first out
      3)PriorityQueue 优先:所设数字越小,优先级越高
    import queue
    
    
    q=queue.Queue(3)
    q.put(1)
    q.put(2)
    q.put(3)
    print(q.get())
    print(q.get())
    print(q.get())
    
    q = queue.LifoQueue(5)
    q.put(1)
    q.put(2)
    q.put(3)
    q.put(4)
    print(q.get())
    
    
    
    q = queue.PriorityQueue()
    q.put((10,'a'))
    q.put((-1,'b'))
    q.put((100,'c'))
    print(q.get())
    print(q.get())
    print(q.get())
    
    
    >>>:
      1
      2
      3
    
      4
    
      (-1, 'b')
      (10, 'a')
      (100, 'c')
    
    
    7.进程池线程池(******) 
      1)socket服务端实现并发
        现在回来想网络编程服务端需要满足哪几点需求
          * 固定的ip和port
          * 24小时提供服务
          * 能够实现并发
      2)进程池线程池介绍
        无论是开线程还是开进程其实都消耗资源,开线程消耗的资源比开进程的小
        线程不可能无限制的开下去,总要消耗和占用资源

        池:  
          为了减缓计算机硬件的压力,避免计算机硬件设备崩溃  
          虽然减轻了计算机硬件的压力,但是一定程度上降低了持续的效率
        进程池线程池:  
          为了限制开设的进程数和线程数,从而保证计算机硬件的安全

    8.如何使用进程池线程池(******)
        * concurrent.futures模块导入
        * 线程池创建(默认:线程数=cpu核数*5左右)
        * submit提交任务(提交任务的两种方式)
        * 异步提交的submit返回值对象
        * shutdown关闭池并等待所有任务运行结束
        * 对象获取任务返回值
        * 进程池的使用,验证进程池在创建的时候里面固定有指定的进程数
        * 异步提交回调函数的使用
    from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
    import time
    import os
    
    # 示例化池对象
    # 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
    pool = ProcessPoolExecutor(5)  # 创建了一个池子,池子里面有5个线程
    
    
    def task(n):
        print(n, os.getpid())
        time.sleep(2)
        return n ** 2
    
    
    def call_back(n):
        print('拿到了结果:%s' % n.result())
    
    
    """
    提交任务的方式
        同步:提交任务之后,原地等待任务的返回结果,再继续执行下一步代码
        异步:提交任务之后,不等待任务的返回结果(通过回调函数拿到返回结果并处理),直接执行下一步操作
    """
    
    # 回调函数:异步提交之后一旦任务有返回结果,自动交给另外一个去执行
    if __name__ == '__main__':
        # pool.submit(task,1)
        t_list = []
        for i in range(20):
            future = pool.submit(task, i).add_done_callback(call_back)  # 异步提交任务
            t_list.append(future)
    
        # pool.shutdown()  # 关闭池子并且等待池子中所有的任务运行完毕
        # for p in t_list:
        #     print('>>>:',p.result())
        print('')
    线程池进程池的使用
    socket并发
    # -------------------服务端----------------------
    import socket
    from threading import Thread
    
    """
    服务端:
        1.固定的ip和port
        2.24小时不间断提供服务
        3.支持高并发
    """
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)  # 半连接池
    
    
    def communicate(conn):
        while True:
            try:
                data = conn.recv(1024)  # 阻塞
                if len(data) == 0: break
                print(data)
                conn.send(data.upper())
            except ConnectionResetError:
                break
        conn.close()
    
    
    while True:
        conn, addr = server.accept()  # 阻塞
        print(addr)
        t = Thread(target=communicate, args=(conn,))
        t.start()
    
    
    
    # -------------------客户端----------------------
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    
    while True:
        info = input('>>>:').encode('utf-8')
        if len(info) == 0: continue
        client.send(info)
        data = client.recv(1024)
        print(data)
     
  • 相关阅读:
    分布式锁没那么难,手把手教你实现 Redis 分布锁!|保姆级教程
    我去,这么简单的条件表达式竟然也有这么多坑
    MySQL 可重复读,差点就让我背上了一个 P0 事故!
    用了这么多年的 Java 泛型,你对它到底有多了解?
    项目一再跳票?试试这一招:用Deadline倒逼生产力
    Code Review最佳实践
    你的大学会有模拟面试吗?一些常见面试问题背后的逻辑是什么?
    从软件工程的角度解读任正非的新年公开信
    记在美国的一次校园招聘
    为什么软件工程教科书上的内容和现实的软件项目之间存在着一定差异?
  • 原文地址:https://www.cnblogs.com/zhouyongv5/p/10834527.html
Copyright © 2020-2023  润新知