8-26-GLI锁与普通互斥锁、死锁问题、递归锁、信号量、Event事件、并发的tcp通信、进程池线程池

昨日回顾

    1 生产者消费者
    	-在生产者和消费者之间,通过队列,增加缓冲,避免了生产者和消费者之间交互
        -Queue,redis,rabbitmq,kafka
        -解耦合,队列是微服务的基础
    
    
    2 线程理论,开启
    	-进程是资源分配的最小单位,线程是执行的最小单位(cpu调度的最小单位),每个进程中最少一个线程
        -两种方式(跟进程完全类似)
    
    
    3 join方法
    	-等待子线程执行结束,线程对象.join()
    
    
    4 线程数据共享
    	-不同线程,变量是可以共用的,查看和修改(数据错乱)
    
    
    5 线程对象其他方法
    	-name:人为设置,有默认
        -ident:线程号
        -active_count():现在还存活多少线程
        -is_alive():此线程是否还存活
    
    
    6 线程互斥锁
    	-不同线程要修改同一个数据,要加锁
        -让并行变成串行,牺牲了效率,保证了数据安全
        -悲观锁,乐观锁,分布式锁


    7 GIL
    	-全局解释器锁:在cpython解释器内部有一把大锁,线程要执行,必须获取到这把锁
        -为什么要有它? python的垃圾回收机制是线程不安全的,所有所有线程要抢到GIL才能执行
        -cpython的多线程不是真正的多线程,同一时刻,只有一个线程在执行,不能利用多核优势
        
        -----以下只针对于cpython解释器
        
            -在单核情况下:
            	-开多线程还是开多进程?不管干什么都是开线程
            -在多核情况下:
            	-如果是计算密集型,需要开进程,能被多个cpu调度执行
                -如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行



今日内容

1 验证GIL锁的存在方式

                from threading import Thread
                from multiprocessing import Process
                
                def task():
                    while True:
                        pass
                
                if __name__ == '__main__':
                    for i in range(6):
                        # t=Thread(target=task)  # 因为有GIL锁,同一时刻,只有一条线程执行,所以cpu不会满
                        t=Process(target=task)   # 由于是多进程,进程中的线程会被cpu调度执行,6个cpu全在工作,就会跑满
                        t.start()



2 GIL与普通互斥锁的区别

    1 GIL锁是不能保证数据的安全,普通互斥锁来保证数据安全
    

                from threading import Thread, Lock
                import time
                
                mutex = Lock()
                money = 100
                
                
                def task():
                    global money
                    mutex.acquire()
                    temp = money
                    time.sleep(1)
                    money = temp - 1
                    mutex.release()
                
                
                if __name__ == '__main__':
                    ll=[]
                    for i in range(10):
                        t = Thread(target=task)
                        t.start()
                        # t.join()  # 这样会变成串行,不能这么做
                        ll.append(t)
                    for t in ll:
                        t.join()
                    print(money)
    


3 io密集型和计算密集型


    -----以下只针对于cpython解释器
    -在单核情况下:
    -开多线程还是开多进程?不管干什么都是开线程
    -在多核情况下:
    -如果是计算密集型,需要开进程,能被多个cpu调度执行
    -如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行
    
    
                
                from threading import Thread
                from multiprocessing import Process
                import time
                
                
    # 计算密集型
                def task():
                    count = 0
                    for i in range(100000000):
                        count += i
                
                
                if __name__ == '__main__':
                    ctime = time.time()
                    ll = []
                    for i in range(10):
                        t = Thread(target=task)  # 开线程:42.68658709526062
                        # t = Process(target=task)   # 开进程:9.04949426651001
                        t.start()
                        ll.append(t)
                
                    for t in ll:
                        t.join()
                    print(time.time()-ctime)
                
    
    # io密集型
                def task():
                    time.sleep(2)
                
                
                if __name__ == '__main__':
                    ctime = time.time()
                    ll = []
                    for i in range(400):
                        t = Thread(target=task)  # 开线程:2.0559656620025635
                        # t = Process(target=task)   # 开进程:9.506720781326294
                        t.start()
                        ll.append(t)
                
                    for t in ll:
                        t.join()
                    print(time.time()-ctime)



4 死锁现象(哲学家就餐问题)

    # 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
   
    # 单例模式:https://www.cnblogs.com/liuqingzheng/p/10038958.html
    
    
    # 死锁现象,张三拿到了A锁,等B锁,李四拿到了B锁,等A锁
                
                from threading import Thread, Lock
                import time
                
                mutexA = Lock()
                mutexB = Lock()
                
                
                def eat_apple(name):
                    mutexA.acquire()
                    print('%s 获取到了a锁' % name)
                    mutexB.acquire()
                    print('%s 获取到了b锁' % name)
                    print('开始吃苹果,并且吃完了')
                    mutexB.release()
                    print('%s 释放了b锁' % name)
                    mutexA.release()
                    print('%s 释放了a锁' % name)
                
                
                def eat_egg(name):
                    mutexB.acquire()
                    print('%s 获取到了b锁' % name)
                    time.sleep(2)
                    mutexA.acquire()
                    print('%s 获取到了a锁' % name)
                    print('开始吃鸡蛋,并且吃完了')
                    mutexA.release()
                    print('%s 释放了a锁' % name)
                    mutexB.release()
                    print('%s 释放了b锁' % name)
                
                
                if __name__ == '__main__':
                    ll = ['egon', 'alex', 'tie_dan']
                    for name in ll:
                        t1 = Thread(target=eat_apple, args=(name,))
                        t2 = Thread(target=eat_egg, args=(name,))
                        t1.start()
                        t2.start()
            
            
            
     
    
    # 递归锁(可重入),同一个人可以多次acquire,每acquire一次,内部计数器加1,每relaese一次,内部计数器减一
    # 只有计数器不为0,其他人都不获得这把锁
    
                from threading import Thread, Lock,RLock
                import time
                
                # 同一把锁
                # mutexA = Lock()
                # mutexB = mutexA
                
                # 使用可重入锁解决(同一把锁)
                # mutexA = RLock()
                # mutexB = mutexA
                mutexA = mutexB =RLock()
                
                def eat_apple(name):
                    mutexA.acquire()
                    print('%s 获取到了a锁' % name)
                    mutexB.acquire()
                    print('%s 获取到了b锁' % name)
                    print('开始吃苹果,并且吃完了')
                    mutexB.release()
                    print('%s 释放了b锁' % name)
                    mutexA.release()
                    print('%s 释放了a锁' % name)
                
                
                def eat_egg(name):
                    mutexB.acquire()
                    print('%s 获取到了b锁' % name)
                    time.sleep(2)
                    mutexA.acquire()
                    print('%s 获取到了a锁' % name)
                    print('开始吃鸡蛋,并且吃完了')
                    mutexA.release()
                    print('%s 释放了a锁' % name)
                    mutexB.release()
                    print('%s 释放了b锁' % name)
                
                
                if __name__ == '__main__':
                    ll = ['egon', 'alex', 'tie_dan']
                    for name in ll:
                        t1 = Thread(target=eat_apple, args=(name,))
                        t2 = Thread(target=eat_egg, args=(name,))
                        t1.start()
                        t2.start()
    


5 Semaphore信号量

    # Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
    
                from  threading import Thread,Semaphore
                import time
                import random
                sm=Semaphore(3) # 数字表示可以同时有多少个线程操作
                
                def task(name):
                    sm.acquire()
                    print('%s 正在厕所位'%name)
                    time.sleep(random.randint(1,5))
                    sm.release()
    


6 Event事件

    # 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
    # 比如一个线程等待另一个线程执行结束再继续执行
    # 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
    # 比如一个线程等待另一个线程执行结束再继续执行
    
                from threading import Thread, Event
                import time
                
                event = Event()
                
                
                def girl(name):
                    print('%s 现在不单身,正在谈恋爱'%name)
                    time.sleep(10)
                    print('%s 分手了,给屌丝男发了信号'%name)
                    event.set()
                
                
                def boy(name):
                    print('%s 在等着女孩分手'%name)
                    event.wait()  # 只要没来信号,就卡在者
                    print('女孩分手了,机会来了,冲啊')
                
                
                if __name__ == '__main__':
                    lyf = Thread(target=girl, args=('刘亦菲',))
                    lyf.start()
                
                    for i in range(10):
                        b = Thread(target=boy, args=('屌丝男%s号' % i,))
                        b.start()
                
    
    # 作业:起两个线程,第一个线程读文件的前半部分,读完发一个信号,另一个进程读后半部分,并打印
    
                from threading import Thread, Event
                import time
                import os
                
                event = Event()
                # 获取文件总大小
                size = os.path.getsize('a.txt')
                
                
                def read_first():
                    with open('a.txt', 'r', encoding='utf-8') as f:
                        n = size // 2  # 取文件一半,整除
                        data = f.read(n)
                        print(data)
                        print('我一半读完了,发了个信号')
                        event.set()
                
                
                def read_last():
                    event.wait()  # 等着发信号
                    with open('a.txt', 'r', encoding='utf-8') as f:
                        n = size // 2  # 取文件一半,整除
                        # 光标从文件开头开始,移动了n个字节,移动到文件一半
                        f.seek(n, 0)
                        data = f.read()
                        print(data)
                
                
                if __name__ == '__main__':
                    t1=Thread(target=read_first)
                    t1.start()
                    t2=Thread(target=read_last)
                    t2.start()



7 线程queue

    
    # 进程queue和线程不是一个
    # from multiprocessing import Queue
    
    # 线程queue
    from queue import Queue,LifoQueue,PriorityQueue
    
    # 线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带
    # queue是线程安全的
    
    
    '''
    三种线程Queue
        -Queue:队列,先进先出
        -PriorityQueue:优先级队列,谁小谁先出
        -LifoQueue:栈,后进先出
    '''
    如何使用
                q=Queue(5)
                q.put("lqz")
                q.put("egon")
                q.put("3号")
                q.put("4号")
                q.put("5号")
                
                
                # q.put("银dan")
                # q.put_nowait("银dan")
                # 取值
                print(q.get())
                print(q.get())
                print(q.get())
                print(q.get())
                print(q.get())
                # 卡住
                # print(q.get())
                # q.get_nowait()
                # 是否满,是否空
                print(q.full())
                print(q.empty())
                
                LifoQueue
                
                q=LifoQueue(5)
                q.put("lqz")
                q.put("egon")
                q.put("3号")
                q.put("4号")
                q.put("5号")
                #
                # q.put("ddddan")
                print(q.get())
                
                
                PriorityQueue:数字越小,级别越高
                
                q=PriorityQueue(3)
                q.put((-10,'1号'))
                q.put((100,'2号'))
                q.put((101,'3号'))
                # q.put((1010,'铁dddan'))  # 不能再放了
                
                print(q.get())
                print(q.get())
                print(q.get())
    
    
    

8 线程池

    1 为什么会出现池?不管是开进程还是开线程,不能无限制开,通过池,假设池子里就有10个,不管再怎么开,永远是这10个
    
    
    2 如何使用
                
                from concurrent.futures import ThreadPoolExecutor
                pool = ThreadPoolExecutor(2)
                pool.submit(get_pages, url).add_done_callback(call_back)
                from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
                from threading import Thread
                import time
                import random
                
                pool = ThreadPoolExecutor(5)  # 数字是池的大小
                # pool = ProcessPoolExecutor(5)  # 数字是池的大小
                
                
                def task(name):
                    print('%s任务开始' % name)
                
                    time.sleep(random.randint(1, 4))
                    print('任务结束')
                    return '%s 返回了'%name
                
                
                
                def call_back(f):
                    # print(type(f))
                    print(f.result())
                if __name__ == '__main__':
                
                    # ll=[]
                    # for i in range(10):  # 起了100个线程
                    #     # t=Thread(target=task)
                    #     # t.start()
                    #     res = pool.submit(task, '屌丝男%s号' % i)  # 不需要再写在args中了
                    #     # res是Future对象
                    #     # from  concurrent.futures._base import Future
                    #     # print(type(res))
                    #     # print(res.result())  # 像join,只要执行result,就会等着结果回来,就变成串行了
                    #     ll.append(res)
                    #
                    # for res in ll:
                    #     print(res.result())
                
                    # 终极使用
                    for i in range(10):  # 起了100个线程
                        # 向线程池中提交一个任务,等任务执行完成,自动回到到call_back函数执行
                        pool.submit(task,'屌丝男%s号' % i).add_done_callback(call_back)
                        
                        
                        
                        
                   from concurrent.futures import ThreadPoolExecutor
                
                import requests  # 爬虫会学到的模块
                
                pool = ThreadPoolExecutor(2)
                
                
                def get_pages(url):
                    # https://www.baidu.com
                    res = requests.get(url)  # 向这个地址发送请求
                
                    name = url.rsplit('/')[-1] + '.html'
                    print(name)  # www.baidu.com.html
                    # res.content拿到页面的二进制
                    return {'name': name, 'text': res.content}
                
                
                def call_back(f):
                    dic = f.result()
                    with open(dic['name'], 'wb') as f:
                        f.write(dic['text'])
                
                
                if __name__ == '__main__':
                    ll = [这里面放网站链接]
                    for url in ll:
                        pool.submit(get_pages, url).add_done_callback(call_back)
    
    

9 进程池

    1 如何使用

                from concurrent.futures import ProcessPoolExecutor
                pool = ProcessPoolExecutor(2)
                pool.submit(get_pages, url).add_done_callback(call_back)



============================其余补充===================================


进程 与 线程 的数据 比较:

    不同的 进程,变量 不能共用, 仅可以查看主进程中的变量,但是不能修改


    线程的数据(即 主线程中的变量)共享:    不同线程,变量是可以共用的,查看和修改(数据错乱)




多线程 与 多进程 的区别:

    1、开启多个进程时: 
            父进程有一个pid, 每一个子进程各有一个pid
            
            即
                创建一个 父进程,
                创建若干个 子进程

    2、开启多个线程时:
            所有的线程(主线程 、子线程) 只有一个pid
            
            即
                创建一个新进程,
                盛放 所有的所有的线程



对于cpython解释器:

    多线程:程序只能在 单 个cpu上, 并发 执行
    
    多进程:程序可在  多 个cpu上, 并行 执行


----应用: 在各种情况下,究竟是开 多进程  or  多线程

        对于 单 核计算机:
            
            由于在一个cpu上都只能进行并发执行,并且线程的开销比进程更小,
            所以程序无论是干什么,都是开多线程


            开多线程还是开多进程?不管干什么都是开线程


        
        对于 多 核计算机:
        
            对于 计算密集型程序: 需要开 多进程, 能被放在多个cpu上调度执行
            
            对于 I/O密集型程序: 由于 存在着频繁的I/O阻塞, 为了系统资源的最大化,只需要开 多线程, 放在一个cpu上进行并发执行即可。
                                
                                如果使用多进程方式, 系统资源消耗更大, 运行所需时间反而会更长,因为 cpu频繁调度进程时,会耗费大量的时间,用来调度进程的资源 




解释性的语言,  线程是 不安全的

    因为是边解释边执行的,不知道当前数据是否是之后用到的

    所以使用了GIL锁,用于将当前的程序中的所有数据都固定住,不被垃圾回收机制回收掉


编译型的语言,  线程 是安全的

    因为一次性编译之后,知道哪些数据是需要用到的,对于用不到的则进行回收




乐观锁 与 悲观锁:

    悲观锁:能保证第一个修改成功

        从一开始,就认为本次 有其他人对数据进行修改,所以一开始就进行加锁


    乐观锁:不能保证修改成功

        从一开始,就认为本次 没有数据进行修改,所以一开始就不进行加锁,
        但当真正要改的时候,如果发现数据被改动了,则取消本次修改



课堂例题中:

    上面一个  for  i  in range(10):
        ...
        是完完全全的串行


    下面一个 for  t in  l1: 变成了并行

        此时没有sleep时,

        并行之后就需要,用到锁





死锁问题: 互相等待对方手中的资源

    多线程时:
        由于多个线程只能在一个cpu上执行,
        一个cpu不是执行它,就是执行另一个,
        所以一旦,双方都需要对方手中的资源,则就会陷入互相等待的局面



递归锁: 把并发的程序,  变成了  串行的程序




Event事件

    event.wait()    # 当前需要等待事件发生,然后再进行下一步

    evert.set()     # 当前 事件发生,可以进行之后的操作




三种线程Queue

    -Queue:队列,先进先出
    
    -PriorityQueue:优先级队列,谁小谁先出
        # q=PriorityQueue(3)
        优先级队列特点: 放入值的时候,需要指定优先级
        # q.put((-10,'金蛋'))
    -LifoQueue:栈,后进先出队列,后进先出



queue注意点:

    # 进程  中的队列  queue (进程queue和线程不是一个)
        from multiprocessing import Queue

    # 线程    queue 是安全的
        from queue import Queue,LifoQueue,PriorityQueue

        # 线程之间通信,因为共享变量会出现数据不安全问题
        # 用线程queue,不需要加锁,内部自带; 线程queue是安全的    


        进程  和 线程中都有,  Queue 
        但是 他们的Queue不是同一个东西, 不能混着用




使用并发的 tcp方式 的socket通信

        以下代码需要写在if __name__ == '__main__':的里面,否则每当开启一个进程的时候,
        __main__ 上面的所有代码,就会被搬运在一个新文件中执行一次,而为服务端指定的ip:端口  是固定的,所以就会出现当前的ip:端口  被占用的报错

            
        使用万能异常的时候,不光要保证即使有错,还能继续执行
        还要能看到程序究竟出现了什么错误,因此以后应按照下面这种方式进行书写,即出现了异常 也要打印出异常

                except Exception as e:
                    print(e)
                    break


        客户端如果发送一个内容固定的字符串时,也可以使用这种方式,即 直接将字符串内容指定为 utf-8编码的byte类型

            client.send(b'hello world')




线程池 、进程池 要点:


    1.
        from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

        process_pool = ProcessPoolExecutor( pool_size )   # 进程池

        thread_pool = ThreadPoolExecutor( pool_size )   # 线程池



    2. 普通方式开启进程/线程,无法获得task中的返回值

        {


                    t=Thread(target=task, args=(f'屌丝男 {i} 号', ))
                    t.start()

            仅仅是效果 等效于

                    res=pool.submit(task, f'屌丝男 {i} 号' )

                    这里的res内容:  <Future at 0x26367ad1d00 state=running>
                                   <Future at 0x26367ae9d00 state=pending>

    

    3. 多进程/多线程 中的任务   带返回值时:   通过 call_back(f) 方法,获取每个进程的结果

        {
            向线程池中提交一个任务,等任务执行完成,自动回到call_back函数执行
          
            pool.submit(task,f'屌丝男 {i}号').add_done_callback(call_back)  # 启动进程 、把进程中任务的结果返回


            call_back函数自行定义:

                def call_back(f):
                    # print(type(f))  # 观察其 类型
                    print(f.result())

        }




    5.
        {

            第一种: 
                    
                    res=pool.submit(task, f'屌丝男 {i} 号' )
                    print(res.result())  # 像join,只要执行result,就会等着结果回来,就变成串行了
                
                表面上类似于下面这种形式,不过上面这种 就变成 串行的了,而下面的则仍然是并发 

            第二种:

                    pool.submit(task, f'屌丝男 {i} 号' ).add_done_callback(call_back)

                仍然是并发运行各个程序(不是串行!!! 和原来无返回值一样),
                并且在运行完之后, task返回的结果 就 传给了call_back, 在call_back中可以对返回的结果进行处理 


        }

==============================================

GIL锁(即 解释器的锁,  一次只能让一个线程获得运行的权限,在解释器上运行) 

加自己的mutex锁,使得数据一次完整的操作,只能是一个线程进行


正常情况,都先释放后获得的锁


可重入: 一个人可以多次获得同一把锁


sm 用于  限制同时 用厕所的人的个数


使用共享变量,会出现数据错乱的问题  所以推荐使用 队列


windows中  开多进程时,一定要把所有的代码都写到 ...main... 中


将所有的 进程/线程对象t,都装进一个列表中,目的:
    为了这些线程都执行完, 最终查看 外部变量的结果


# 开辟相同数量的 进程 和 线程,进程的花销比线程要大
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值