zoukankan      html  css  js  c++  java
  • 线程 *知识点扩充

    1.  基于 TCP 协议实现 socket 通信 多线程并发 效果(简易版本)

    
    
    # 客户端
    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8083))
    
    while True:
        client.send(b'hello')
        data = client.recv(1024)
        print(data.decode('utf-8'))
    
    
    # 服务端
    TCP服务端实现并发
      1.将不同的功能尽量拆分成不同的函数
        拆分出来的功能可以被多个地方使用
      
      1.将连接循环和通信循环拆分成不同的函数
      2.将通信循环做成多线程
    import socket from threading import Thread def run(conn, i): while True: try: data = conn.recv(1024) print('第%s条数据:' % i, data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() server = socket.socket() server.bind(('127.0.0.1', 8083)) server.listen(5) while True: conn, addr = server.accept() for i in range(10): t = Thread(target=run, args=(conn, i)) t.start()
    
    

     2. 全局解释器锁 GIL(global interpreter lock)

      GIL本质也是一把互斥锁:将并发变成串行牺牲效率,保证数据的安全

      作用:阻止同一个进程下的多个线程的同时执行

          也就意味着 同一个进程内多个线程无法实现并行但是可以实现并发

          python的多线程没法利用多核优势  是不是就是没有用了?   

              研究python的多线程是否有用需要分情况讨论:(肯定是有用的)

                 假设有四个任务(每个任务都需要10s)

                  都是计算密集型:

                    单核情况下
                          开线程更省资源
                    多核情况下
                          开进程 10+s
                          开线程 40+s

                  都是 IO 密集型 :
                    单核情况下
                          开线程更节省资源
                    多核情况下

                       开线程更节省资源

      PS:python 解释器有很多种,最常见的就是 CPython 解释器   在CPython解释器才有GIL的概念,不是python的特点

         GIL 的存在是因为 CPython 解释器的 内存管理的线程不是安全 的      

           内存管理机制:
              引用计数值与变量的绑定关系的个数
              标记清除:当内存快要满的时候 会自动停止程序的运行 检测所有的变量与值的绑定关系
                      给没有绑定关系的值打上标记,最后一次性清除
              分代回收:(垃圾回收机制也是需要消耗资源的,而正常一个程序的运行内部会使用到很多变量与值
                        并且有一部分类似于常量,减少垃圾回收消耗的时间,应该对变量与值的绑定关系做一个分类)

                   新生代(5S)    >>>   青春代(10s)    >>>   老年代(20s)
                     垃圾回收机制扫描一定次数发现关系还在,会将该对关系移至下一代
                     随着代数的递增 扫描频率是降低的

            如:多个线程同时处理同一进程下的一份数据,假如没有全局解释器锁,容易造成数据的错乱

    # 计算密集型
    # 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())  # 本机为6核
    #     start=time.time()
    #     for i in range(6):
    #         # p=Process(target=work) #耗时  4.732933044433594
    #         p=Thread(target=work) #耗时 22.83087730407715
    #         l.append(p)
    #         p.start()
    #     for p in l:
    #         p.join()
    #     stop=time.time()
    #     print('run time is %s' %(stop-start))
    # IO密集型
    from multiprocessing import Process
    from threading import Thread
    import threading
    import os,time
    def work():
        time.sleep(2)
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count()) #本机为6核
        start=time.time()
        for i in range(4000):
            p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
            # p=Thread(target=work) #耗时2.051966667175293s多
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))

    3. 全局解释器 GIL & 普通互斥锁

    我们从 全局解释器锁  未加锁但是让程序出现IO操作情况  以及  加锁 情况来比较
    # 全局解释器锁
    
    def run(i):
        global n
        re_n = n
        n = re_n - 1
        print(f'第{i}个线程修改了n,现在 n = {n}')
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=run, args=(i,))
            t.start()
    
    
    # 由于有全局解释器锁的存在,多线程操作数据正常
    
    '''
    # 第0个线程修改了n,现在n = 99
            # 第1个线程修改了n,现在n = 98
            # 第2个线程修改了n,现在n = 97
            # 第3个线程修改了n,现在n = 96
            # 第4个线程修改了n,现在n = 95
            # 第5个线程修改了n,现在n = 94
            # 第6个线程修改了n,现在n = 93
            # 第7个线程修改了n,现在n = 92
            # 第8个线程修改了n,现在n = 91
            # 第9个线程修改了n,现在n = 90
            # 
            # Process finished with exit code 0
    '''
    # 不加锁但是 让程序出现IO操作
    
    from threading import Thread
    import time
    
    n = 100
    
    
    def run(i):
        global n
        re_n = n
        time.sleep(1)  # IO操作时候回自动释放GIL
        n = re_n - 1
        print(f'第{i}个线程修改了n,现在 n = {n}')
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=run, args=(i,))
            t.start()
    
    # 由于多线程程序遇到了IO操作,使得数据错乱
    '''
        # 第1个线程修改了n,现在 n = 99
            # 第0个线程修改了n,现在 n = 99
            # 第6个线程修改了n,现在 n = 99
            # 第4个线程修改了n,现在 n = 99
            # 第2个线程修改了n,现在 n = 99
            # 第3个线程修改了n,现在 n = 99
            # 第7个线程修改了n,现在 n = 99
            # 第5个线程修改了n,现在 n = 99
            # 第8个线程修改了n,现在 n = 99
            # 第9个线程修改了n,现在 n = 99
            # 
            # Process finished with exit code 0
    '''
    # 加锁
    from threading import Thread, Lock
    import time
    
    
    mutex = Lock()  # 生成一把锁
    
    n = 100
    
    
    def run(i):
        mutex.acquire()  # 抢锁
        global n  # 局部修改全局
        re_n = n
        time.sleep(1)  # IO操作时候回自动释放GIL,但是由于自己加了一把锁,所以还是在修改数据时候并发变成串行
        n = re_n - 1
        print(f'第{i}个线程修改了n,现在 n = {n}')
        mutex.release()  # 释放锁
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=run, args=(i,))
            t.start()
    
    # 加锁情况下,数据得到了保障,结果正常  此时同时有 GIL 和 锁
    '''
    第0个线程修改了n,现在 n = 99
    第1个线程修改了n,现在 n = 98
    第2个线程修改了n,现在 n = 97
    第3个线程修改了n,现在 n = 96
    第4个线程修改了n,现在 n = 95
    第5个线程修改了n,现在 n = 94
    第6个线程修改了n,现在 n = 93
    第7个线程修改了n,现在 n = 92
    第8个线程修改了n,现在 n = 91
    第9个线程修改了n,现在 n = 90
    
    Process finished with exit code 0
    '''

     4. 死锁现象(了解知识点)

    from threading import Thread, Lock
    import time
    
    mutex1 = Lock()
    mutex2 = Lock()
    
    def run(i):
        run1(i)
        run2(i)
    
    
    def run1(i):
        mutex1.acquire()
        print(f'''
        
    第 {i} 个线程 第一次 抢到了 mutex1 锁''')
        mutex2.acquire()
        print(f'第 {i} 个线程 第一次 抢到了 mutex2 锁')
        mutex2.release()
        mutex1.release()
    
    
    def run2(i):
        mutex2.acquire()
        print(f'''
        
    第 {i} 个线程 第二次 抢到了 mutex2 锁''')
        time.sleep(1)
        mutex1.acquire()
        print(f'第 {i} 个线程 第二次 抢到了 mutex1 锁')
        mutex1.release()
        mutex2.release()
    
    
    if __name__ == '__main__':
        for i in range(5):
            t = Thread(target=run, args=(i,))
            t.start()
    
    # 结果
    '''
    第 0 个线程 第一次 抢到了 mutex1 锁
    第 0 个线程 第一次 抢到了 mutex2 锁
    
        
    第 0 个线程 第二次 抢到了 mutex2 锁
    
        
    第 1 个线程 第一次 抢到了 mutex1 锁
    '''
    # 上述结果可以看到,由于第0个线程在第二次抢锁时候,刚抢完锁1,准备抢锁2时候,发现锁2已经被线程1给抢走了,只能继续停在原地,代码执行不动,也就是死锁
    
    
    # RLock(递归锁)
    """
    Rlock可以被第一个抢到锁的人连续的acquire和release
    每acquire一次锁身上的计数加1
    每release一次锁身上的计数减1
    只要锁的计数不为0 其他人都不能抢
    
    """
    from threading import Thread, RLock
    import time
    
    
    mutex1 = mutex2 = RLock()  # Rlock可以被第一个抢到锁的人连续的acquire和release
    # 此时 mutex1 和 mutex2 是同一把锁
    
    def run(i):
        run1(i)
        run2(i)
    
    
    def run1(i):
        mutex1.acquire()
        print(f'''
        
    第 {i} 个线程 第一次 抢到了 mutex1 锁''')
        mutex2.acquire()
        print(f'第 {i} 个线程 第一次 抢到了 mutex2 锁')
        mutex2.release()
        mutex1.release()
    
    
    def run2(i):
        mutex2.acquire()
        print(f'''
        
    第 {i} 个线程 第二次 抢到了 mutex2 锁''')
        time.sleep(1)
        mutex1.acquire()
        print(f'第 {i} 个线程 第二次 抢到了 mutex1 锁')
        mutex1.release()
        mutex2.release()
    
    
    if __name__ == '__main__':
        for i in range(5):
            t = Thread(target=run, args=(i,))
            t.start()
    
    # 结果
    第 0 个线程 第一次 抢到了 mutex1 锁
    第 0 个线程 第一次 抢到了 mutex2 锁
    
        
    第 0 个线程 第二次 抢到了 mutex2 锁
    第 0 个线程 第二次 抢到了 mutex1 锁
    
        
    第 1 个线程 第一次 抢到了 mutex1 锁
    第 1 个线程 第一次 抢到了 mutex2 锁
    
        
    第 1 个线程 第二次 抢到了 mutex2 锁
    第 1 个线程 第二次 抢到了 mutex1 锁
    
        
    第 3 个线程 第一次 抢到了 mutex1 锁
    第 3 个线程 第一次 抢到了 mutex2 锁
    
        
    第 3 个线程 第二次 抢到了 mutex2 锁
    第 3 个线程 第二次 抢到了 mutex1 锁
    
        
    第 2 个线程 第一次 抢到了 mutex1 锁
    第 2 个线程 第一次 抢到了 mutex2 锁
    
        
    第 2 个线程 第二次 抢到了 mutex2 锁
    第 2 个线程 第二次 抢到了 mutex1 锁
    
        
    第 4 个线程 第一次 抢到了 mutex1 锁
    第 4 个线程 第一次 抢到了 mutex2 锁
    
        
    第 4 个线程 第二次 抢到了 mutex2 锁
    第 4 个线程 第二次 抢到了 mutex1 锁
    
    Process finished with exit code 0
    
    

     5. 信号量(了解知识点)

    from threading import Semaphore, Thread
    import time, random
    
    sm = Semaphore(5)  # 5表示容量个数
    
    
    def run(i):
        sm.acquire()  # 能同时接纳5个
        print(f'{i}占了一个位置')
        time.sleep(random.randint(1, 3))
        sm.release()  # 释放一个空出来一个,可以继续接纳
        print(f'{i}释放了一个位置')
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=run, args=(i,))
            t.start()
    
    # 结果
    0占了一个位置
    1占了一个位置
    2占了一个位置
    3占了一个位置
    4占了一个位置
    3释放了一个位置
        
    5占了一个位置
    1释放了一个位置
        
    6占了一个位置
    0释放了一个位置
    7占了一个位置
    2释放了一个位置
        
    8占了一个位置
    
    4释放了一个位置
        
    6释放了一个位置
        
    9占了一个位置
    5释放了一个位置
        
    9释放了一个位置
        
    8释放了一个位置
        
    7释放了一个位置
        
    
    Process finished with exit code 0

     6. event 事件(了解知识点)

    from threading import Event, Thread
    import time
    
    e = Event()  # 生成一个Event对象
    
    
    def run():
        print('      红灯....')
        time.sleep(3)
        e.set()  # 发信号
        print('      绿灯... ')
    
    
    t = Thread(target=run)
    t.start()
    
    
    def run1(i):
        print(f'公交 {i + 1} 路停车等待....')
        e.wait()  # 等待信号
        print(f'公交 {i + 1} 路开始百公里加速....')
    
    
    for i in range(10):
        t = Thread(target=run1, args=(i,))
        t.start()
    
    # 结果
    '''
          红灯....
    公交 1 路停车等待....
    公交 2 路停车等待....
    公交 3 路停车等待....
    公交 4 路停车等待....
    公交 5 路停车等待....
    公交 6 路停车等待....
    公交 7 路停车等待....
    公交 8 路停车等待....
    公交 9 路停车等待....
    公交 10 路停车等待....
          绿灯... 
    公交 1 路开始百公里加速....
    公交 4 路开始百公里加速....
    公交 5 路开始百公里加速....
    公交 8 路开始百公里加速....
    公交 9 路开始百公里加速....
    公交 2 路开始百公里加速....
    公交 10 路开始百公里加速....
    公交 6 路开始百公里加速....
    公交 3 路开始百公里加速....
    公交 7 路开始百公里加速....
    
    Process finished with exit code 0
    '''

    7. 线程 q(了解知识点)

    from queue import Queue, LifoQueue, PriorityQueue
    
    
    q1 = Queue()  # 先进先出队列
    q1.put(2)  # 先放
    q1.put(1)
    q1.put(3)
    
    print(q1.get())  # 2
    
    q2 = LifoQueue()  # 后进先出队列 last in first out
    q2.put(2)
    q2.put(3)
    q2.put(5)  # 后放
    
    print(q2.get())  # 5
    
    q3 = PriorityQueue()  # 优先级队列,一个元祖参数,元祖内第一个参数表示优先级,越小越优先,支持负数;元组内第二个参数为要放入的数据
    q3.put((10, 4))
    q3.put((20, 5))
    q3.put((-10, 6))  # 优先级最高
    q3.put((0, 7))
    
    print(q3.get())  # (-10, 6)
    print(q3.get()[1])  # 6 元祖是有序的,支持索引取值
  • 相关阅读:
    Android零碎知识之Style and Theme
    Java 线程池(一):开篇及Executor整体框架介绍
    ADB命令笔记本
    volatile变量理解 via《Java并发编程实战》
    Java NIO
    乱七八糟(一)
    Android事件分发机制
    AndroidStudio下的依赖管理
    shell--填过的坑
    兼容类问题
  • 原文地址:https://www.cnblogs.com/pupy/p/11354429.html
Copyright © 2011-2022 走看看