Python_并发编程_线程多任务_进程和线程_锁_队列_生产者和消费者模式(14)

目录:

一、多任务
1、概述
2、串行,并发,并行,同步异步
二、进行和线程
1、进程,程序
2、线程
3、进程和线程之间的关系
三、Python 线程实现多任务
1、传统函数执行
2、使用 threading 线程管理 [ 函数式 类方式]
3、查看线程先后顺序
4、资源独占join()
5、守护线程 setDaemon() 方法
6、threading模块中提供的方法
7、使用继承方式开始线程
8、线程共享全局变量
9、多线程开发可能会遇到的问题
四、互斥锁
1、生活中阐述线程锁
2、解决线程数据冲突
2、生活中阐述线程锁
五、死锁
六、队列
七、生产和消费者
1、什么是生产者消费者
2、生产者消费者实例
八、threadLocal
九、全局解释锁[ 引用知乎博主 静海听风]
十、Python 进程

创作不易,各位看官,点个赞收藏、关注必回关、QAQ、您的点赞是我更新的最大动力!


一、多任务

(一)概述
多任务含义
  • 生活中:一边听歌,一遍跳舞,开车手操控方向盘,眼睛看路,脚踩油门
  • 电脑:同时运行多个应用程序,例如qq,微信,浏览器等等,同时在运行

1.电脑实现多任务的原理
例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了
在这里插入图片描述
2.单核,双核CPU介绍

  • 单核CPU指的是CPU中有一个核心(形象理解CPU是人的头,核心是头里面包含的大脑),用来处理程序
  • 双核/四核CPU就是CPU中有2个或者4个核心,相当于有2个单核CPU或者是4个单核CPU

在这里插入图片描述
3.查看CPU
电脑–>属性–>设备管理器–>处理器,有 4 个表示一个 CPU 中有 4 个核心
在这里插入图片描述
2.30GHz表示运算速度,越高越好。例如1s中可以计算多少次4.在Python中实现多任务可以使用进程和线程,但是在实际应用中多线程是使用最多的,包括后面我们要学习的爬虫,一般也都是使用线程,当然实际开发中基本都是使用多线程框架来去实现我们的任务,但是在面试过程中经常会被问道线程进程的区别,这里重点讲线程进程的区别和线程的使用。

(二)串行,并发,并行,同步,异步

1.串行
多个任务依次执行,上一个任务没有完成时不能执行后续的任务,最明显的同步执行过程
在这里插入图片描述
2.并发(同时出发,不同的人步调不一定一致)
多个任务依次执行,执行过程中多个任务可以替换执行,在某一个时刻是一个任务在执行,但是在某个时间段内是多个任务在执行。
在这里插入图片描述
3.并行(一起出行,不同的人步调一致—>一二三四)
多个任务没有顺序,同时执行,最终的执行结果跟耗时最长的任务有关
在这里插入图片描述
4.同步和异步

  • 同步,描述的就是串行执行过程,多个任务按照顺序依次执行的过程
  • 异步,描述的就是并发和并行的过程,就是多个任务在一个时间段内同时执行,每个任务都
    不会等待其他任务执行完成后执行。

在这里插入图片描述
计算机中的多任务实现方式:
➔ 多进程实现的多任务操作:并行执行(默认)
➔ 多线程实现的多任务操作:并发执行(默认)
➔ 协程实现的多任务操作:事件处理并发执行(默认)

返回文章目录

二、进程和线程

(一)进程

  • 进程:正在运行的一个程序,可以说是一个进程,是系统进行资源分配和调用的独立单元,每一个进程都有自己独立的内存空间和系统资源。

  • 程序:运行起来的应用程序就称为进程,也就是当程序不运行的时候我们称为程序,当程序运行起来他就是一个进程,通俗的理解就是不运行的时候是程序,运行起来就是进程。程序只有一个,但是进程有多个

(二)线程

  • 线程:线程是进程中的一条执行线路或流程,程序执行的最小单位,线程是任务调度的最小单位

由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程 即线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候调 用的是线程)每一个进程中至少有一个线程

(三)进程和线程之间的关系

  • 进程和线程关系: 一个进程可以有一个或者多个线程,但是一个线程只属于一个进程,一个进程中的多个线程是一种竞争关系

在这里插入图片描述

打开酷狗 [ 程序],开闭空间操作[ 进程],听歌同时下载歌曲[ 2个线程]

返回文章目录

三、Python 线程实现多任务 [ 多线程管理]

1.传统函数执行

无法实现同步进行,必须自顶向下的逻辑运行


# 一边下载歌曲,一边听歌 
import time

def listen():
    for i in range(1,6):
        print("正在听歌")
        time.sleep(1)

def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)
        
down_load()
listen()

在这里插入图片描述
2.使用 threading 线程管理 [ 函数式 类方式]

可以实现,两个线程同时进行,解决了传统执行的尴尬,生活中我们经常,边下载歌曲,边听歌。或者边吃饭,边看手机

注意,结果根据处理,每次启动程序可能出现不同的先后顺序,并无关系

  • 函数式线程使用、基本语法

      	 	import threading # 导入线程
      	 	创建两个线程
      		t1 = threading.Thread(target=test)  #test 为函数名称
      		t2 = threading.Thread(target=test)  #test 为函数名称
      		# 设置线程名称
      		t1.name = "线程1----------"
      		t2.name = "线程2"
      		# 启动线程
      		t1.start()
      		t2.start()
    
  • 类型方式使用线程、基本语法

      	import threading # 导入线程
      	class  Function(threading.Thread)
      	# 创建线程对象
      	t1 = Function()
      	t2 =  Function()
      	# 启动线程
      	t1.start()
      	t2.start()
    

函数式

# 创建线程对象
t1 = MyCounter()
t2 = MyCounter()
# 启动线程
t1.start()
t2.start()

# 一边下载歌曲,一边听歌
import time
import threading

def listen():
    for i in range(1,6):
        print("正在听歌")
        time.sleep(1)

def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)

# down_load()
# listen()

if __name__ == '__main__':
    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)
    # 执行线程
    t1.start()
    t2.start()

在这里插入图片描述
类方式

class MyCounter(threading.Thread):
    """自定义线程类型,继承threading.Thread类"""
    # 类属性
    num = 1

    def run(self):
        """重写run方法,在线程start()启动后会自动执行"""
        while True:
            MyCounter.num += 1
            print(f"{threading.current_thread().getName()} num1: {MyCounter.num}")
            time.sleep(1)
# 不取名字有自己的默认名字

# 创建线程对象
t1 = MyCounter()
t2 = MyCounter()
# 启动线程
t1.start()
t2.start()

在这里插入图片描述
3.线程查看执行顺序




import threading
import time

def test1():
    for i in range(1,6):
        time.sleep(1)
        print("--子线程1--%d"%i)
        print("子线程1中查看线程情况:",threading.enumerate())

def test2():
    for i in range(1,6):
        time.sleep(1)
        print("--子线程2--%d"%i)
        print("子线程2中查看线程情况:",threading.enumerate())
def main():
    print("创建线程之前的线程情况:",threading.enumerate())

    #创建线程
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    time.sleep(1)
    print("创建线程之后的线程情况:", threading.enumerate())

    #开启线程
    t1.start()
    t2.start()
    time.sleep(1)
    print("调用start之后的线程情况:", threading.enumerate())
    time.sleep(6)
    print("等待子线程执行结束后的线程情况:", threading.enumerate())
if __name__ == '__main__':
    main()

在这里插入图片描述

4.资源独占join()

	    线程资源独占方法独占 CPU 时间片
    join()
    功能:当前线程执行完之后,其它线程才执行

    多线程 资源独占
    1、程序中有主线程、子线程,但是多个线程的执行过程,没有顺序。
        那个执行可以执行由CPU管理的!

    2、子线程没有执行结束时,主线程不会退出
        子线程没有执行结束时,主线程中的代码可以全部执行。
        一直到所有的子线程全部运行完毕,主线程才能退出[当前程序]

    3、线程资源独占:强制独占-当前时刻已经启动的线程占用所有资源
        其他线程排队,提升部分线程的优先级,将并发执行变成同步执行
# 一边下载歌曲,一边听歌
import time
import threading

def listen(num):
    for i in range(1,num):
        print(f"正在听歌--{i}")
        time.sleep(1)

def down_load(num,num2):
    print(f"第二个参数{num2}")
    for i in range(1,num):
        print(f"正在下载歌曲--{i}")
        time.sleep(1)

# down_load()
# listen()

def main():
    # 新增需求:下载完歌曲,才能听歌

    # 创建线程
    t1 = threading.Thread(target=down_load,args=(5,6)) # args = (参数1,参数2)
    t2 = threading.Thread(target=listen,args=(5,))
    # 执行线程
    t1.start()
    t1.join() # 设定第一个线程,下载歌曲执行完才能 到线程二 听歌,并且 t1线程结束后 才执行主线程
    t2.start()
    t2.join() # t1,t2线程执行完后才回到主线程执行

if __name__ == '__main__':
    main() # 线程 执行
    print("程序执行结束了")

5.守护线程 setDaemon()

  • setDaemon()将当前线程设置成守护线程来守护主线程

  • 当主线程结束后,守护线程也就结束,不管是否执行完成

  • 应用场景: qq 多个聊天窗口,就是守护线程

  • 注意,需要在子线程开启的时候设置成守护线程,否则无效

  • 主线程必须要等待子线程结束了,才能退出吗?

  • 如 QQ 软件(主线程):聊天窗口(子线程);直接关闭 QQ 主窗口,同时自动关闭聊天窗口?

  • 好像主线程没有等待子线程结束,而是自己结束的时候,不论子线程是否结束直接退出!

  • 主线程如果不等待子线程运行结束,可以直接结束并自动关闭任何状态的子线程

  • 此时:该主线程可以关闭的子线程:是属于自己的守护线程!

需求:用户正在听歌和下载歌曲,关闭QQ音乐软件,应该伴随软件关闭,音乐和下载都关闭

没有守护线程效果

"""
    setDaemon()
    守护线程
"""

# 一边下载歌曲,一边听歌
import time
import threading

def listen():
    for i in range(1,6):
        print("正在听歌")
        time.sleep(1)

def down_load():
    for i in range(1,6):
        print(f"正在下载歌曲")
        time.sleep(1)

def main():

    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)


    # 执行线程
    t1.start()
    t2.start()

if __name__ == '__main__':

    main()
    print("QQ音乐软件关闭了") # 主程序
    

在这里插入图片描述
开启守护线程效果

"""
    setDaemon()
    守护线程
"""

# 一边下载歌曲,一边听歌
import time
import threading

def listen():
    for i in range(1,6):
        print("正在听歌")
        time.sleep(1)

def down_load():
    for i in range(1,6):
        print(f"正在下载歌曲")
        time.sleep(1)

def main():

    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)

    # 守护线程
    t1.setDaemon(True)
    t2.setDaemon(True)

    # 执行线程
    t1.start()
    t2.start()

if __name__ == '__main__':

    main()
    print("QQ音乐软件关闭了") # 主程序

在这里插入图片描述
两个线程,开启一个守护线程

1、将下载歌曲线程设置成 6 次,听歌线程设置 3次
2、下载歌曲线程为t1, 听歌为 t2
3、下载歌曲线程为普通线程t1,听歌线程为守护线程 t2
"""
    setDaemon()
    守护线程
"""

# 一边下载歌曲,一边听歌
import time
import threading


def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)

def listen():
    for i in range(1,3):
        print("正在听歌")
        time.sleep(1)



def main():

    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)

    # 守护线程
    # t1.setDaemon(True)
    t2.setDaemon(True)

    # 执行线程
    t1.start()
    t2.start()

if __name__ == '__main__':

    main()
    print("QQ音乐软件关闭了") # 主程序

在这里插入图片描述
在这里插入图片描述
6.threading模块提供的方法

- threading模块提供的方法

  - threading.currentThread():返回当前的线程变量
  - threading.enumerate():返回一个包含所有正在运行的线程list。正在运行的线程:启动后,结束前,不包括启动前和终止后的线程
  - threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果

  - 线程.getName():获取线程名称
  - 线程.setName():设置线程名称
  - 线程.is_alive():判断线程存活状态

threading.currentThread():返回当前的线程变量

# 一边下载歌曲,一边听歌
import time
import threading


def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)

def listen():
    for i in range(1,3):
        print("正在听歌")
        time.sleep(1)
def main():

    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)

    # 执行线程
    t1.start()
    t2.start()

    # 查看当前线程
    print("当前线程:",threading.current_thread())

if __name__ == '__main__':

    main()
    print("QQ音乐软件关闭了") # 主线程
    # 查看当前线程
    print("当前线程:",threading.current_thread())

在这里插入图片描述
threading.enumerate() 返回线程列表

# 一边下载歌曲,一边听歌
import time
import threading


def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)

def listen():
    for i in range(1,3):
        print("正在听歌")
        time.sleep(1)
def main():

    # 创建线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)

    # 执行线程
    t1.start()
    t2.start()

    # 返回正在运行的线程列表。正在启动运行的线程:启动后,结束前,不包括启动前和终止后的线程
    print("当前线程:",threading.enumerate())


if __name__ == '__main__':

    main()
    print("QQ音乐软件关闭了") # 主线程
    # 查看当前线程

在这里插入图片描述
threading.activeCount() 返回存活总数
在这里插入图片描述
在这里插入图片描述
threading.getName()和setName 返回线程列表
在这里插入图片描述
is_alive()判断线程是否存活
在这里插入图片描述
在这里插入图片描述

7.线程共享全局变量

1、先看简单的线程全局变量变化


import threading
import time


def work1(g_nums):
    g_nums.append(44)
    print(f"--in work1 g_num is {g_nums}")


def work2(g_nums):
    time.sleep(0.5)
    print(f"--in work2 g_num is {g_nums}")


g_nums = [11,22,33]
t1 = threading.Thread(target=work1,args=(g_nums,))
t1.start()
t2 = threading.Thread(target=work2,args=(g_nums,))
t2.start()

在这里插入图片描述
可以看到调用全局变量的时候,仅仅在work1函数中,修改了添加了一个“44”变量,但是在work2中的线程调用全局变量的时候,全局变量数据一致

2、根据休眠顺序修改全局变量的数据

 谁先修改全局注意观察
 休眠的线程会让另外个没有休眠的线程执行获取全局变量

在这里插入图片描述
在这里插入图片描述
现在将线程work1休眠0.5秒
在这里插入图片描述
在0.5秒的休眠时候,足够让线程2先获得全局变量
在这里插入图片描述
可能发现还没有特别的改变,现在将线程wokr2,开启修改全局变量
在这里插入图片描述
我们对比一下,同样的work1 和 work2 也开启了修改,我们来看下谁先修改
在这里插入图片描述
发现线程2 修改完毕后,才轮到线程1进行修改,因为线程1,休眠了0.5秒

8.多线程开发可能会遇到的问题

  • 线程开发会遇到什么问题了?
  • 真正意义上,多进程运行的时候是有切换的,只是效果不明显
"""
import threading
import time

g_num = 0

def work1(num):
    global  g_num
    for i in range(num):
        g_num +=1
    print(f"-- in work1,g_num is {g_num}")

def work2(num):
    global  g_num
    for i in range(num):
        g_num +=1
    print(f"-- in work2,g_num is {g_num}")

print("创建线程之前g_num:",g_num)
t1 = threading.Thread(target=work1,args=(100,)) # 100看不出来
t1.start()
t2 = threading.Thread(target=work2,args=(100,))
t2.start()

在这里插入图片描述
我们不是启动了两个线程吗?t1,t2,但是为甚么t2为什么并没有看到,周期太短的问题,传入参数的时候为100,并不能很明显的看出来。现在将参数调高一点,注意,每个人的电脑CPU反应不同,所以需要传入的参数不同,自行调试
在这里插入图片描述
在这里插入图片描述
线程在进行运行操作的时候,其实都有进行切换的,抢占CPU运算资源,我们作为人是观察不到计算机的运行,但是,实际存在在这种情况的,可能前一秒在t1 修改全局变量,下一秒就跑到t2,t1修改的时候如果本身数据为93
但是t2可能抢到了,可能刚开始起步加,数据为101,于是下一次t1 获取的全局变量又变成了101,双方都存在这种情况。那么可能t2在修改的时候修改到了152,但是t1修改才97,造成数据访问冲突,调用冲突,可能将全局变量数据又变成了97,t2又需要重新加

这就是线程抢占资源,线程出现的情况,那么如何避免这种情况呢?
在这里插入图片描述
join()线程资源独占!!!!!!!!!!严格意义上是单线程,串行
或者让某个线程休眠一下

返回文章目录

四、互斥锁

  • 当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个 线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。

  • 使用过程,应用场景:某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将 资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。

  • 互斥锁保证了 每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性

  • 互斥锁为资源引入的一个状态:锁定/非锁定

     import threading
    
     # 创建锁
     mutex = threading.Lock()
     
     # 锁定,上锁
     mutex.acquire()
     
     # 释放,解锁
     mutex.release()
    
     #注意:
     		1.如果这个锁之前没有上锁,那么acquire不会堵塞 
     		2.如果在调用acquire,这个锁上锁之前,他已经被其它线程上锁,此时acquire会堵塞,
     		直到锁被解锁为止
    

(一)生活中类比线程锁

  • 多线程访问数据,会出现数据冲突的问题,应该怎么解决?
  • 类似生活中,我们可以针对正在访问的数据进行锁定,某一个时刻只能让当前线程访问数据,
  • 直到当前线程开锁(释放锁),下一个线程获取锁(进门),下一个线程才能访问数据
    在这里插入图片描述

(二)解决线程数据冲突问题

在上面的时候,知道了为什么会多线程访问修改全局数据,这是因为抢占资源的原因

import threading
import time
# 使用互斥锁完成2个线程对同一全局变量操作
g_num = 0

def work1(num):
    global  g_num
    for i in range(num):
        # 锁定,上锁
        mutex.acquire()
        g_num +=1
        # 释放,解锁
        mutex.release()
    print(f"-- in work1,g_num is {g_num}")

def work2(num):
    global  g_num
    for i in range(num):
        # 锁定,上锁
        mutex.acquire()
        g_num +=1
        # 释放,解锁
        mutex.release()
    print(f"-- in work2,g_num is {g_num}")

# 创建锁
mutex = threading.Lock()


t1 = threading.Thread(target=work1,args=(1000000,)) # 100看不出来 根据每个用户CPU不同调数值
t1.start()
t2 = threading.Thread(target=work2,args=(1000000,))
t2.start()

在这里插入图片描述
咦,结果怎么只有线程2,成功的加到了2百万了?上锁是将数据资源锁定,不会出现抢占资源,在前面的时候

  • 线程1调用修改数据的时候,t2可能也在调用
  • 上锁后,轮到每个线程的时候,数据修改完毕才释放
  • 两个线程都需要执行一百万次,总合二百万次
  • 线程t1,先执行自增数据,线程t2,后执行修改自增数据
  • 所以线程t2 应该输出的是最终数据二百万,t1数据数据小于二百万
  • 那么有问题了?为啥差距那么多,因为t1,t2不是说修改一次就相互给予
  • 可能t1修改个1000次才给t2,t2修改20000次,但是总和是二百万

返回文章目录

五、死锁

  • 在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源, 就会造成死锁现象。如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套
  • 男女朋友吵架,双方都想对方道歉,结果一致等待,死循环,死锁

# 在多个线程共享资源的时候,如果两个线程分别占有一部分资源,
# 并且同时等待对方的资源, 就会造成死锁现象,尽管死锁很少发生,
# 但是一旦发生就会造成应用程序停止相应

import threading
# #创建锁
# mutex = threading.Lock()
#
# #锁定
# mutex.acquire()
#
# #释放
# mutex.release()

import time

def test1():
    #lock1上锁
    lock1.acquire()
    print("--test1开始执行--")
    time.sleep(1)
    # lock2上锁
    lock2.acquire()
    print("--test1执行完成--")
    lock2.release() #lock2解锁
    lock1.release() #lock1解锁

def test2():
    # lock2上锁
    lock2.acquire()
    print("--test2开始执行--")
    time.sleep(1)
    # lock1上锁
    lock1.acquire()
    print("--test2执行完成--")
    lock1.release()  # lock1解锁
    lock2.release()  # lock2解锁
lock1 = threading.Lock()
lock2 = threading.Lock()
if __name__ == '__main__':
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    t2.start()

看到程序一直还在执行,进入了死循环
在这里插入图片描述
返回文章目录

六、队列

  • 程序中的各种存储数据的容器,在多线程访问数据时,肯定会出现数据访问冲突吗?
  • 非线程安全:不是指这个线程不安全,而是指多个线程同时访问数据容易出现冲突
  • 变量(整数、浮点数、字符串)、组合数据类型都是线程非安全
  • 线程安全:多个线程同时访问某个容器中的数据时,这个容器本身就提供了对数据的保护,
  • 多个线程访问时不会出现冲突问题,如队列
队列:就是一个典型的线程安全的容器
from queue import Queue

在这里插入图片描述

一定要先导入模块

from queue import Queue
# 创建队列对象
q = Queue(3) # 队列存储3个数据
q2 = Queue(maxsuze=3) # 队列存储 3个数据
名称 解释 用法
put() 向队列存放数据依次排队 ,如果此方法阻塞,直到队列可用为止 q.put(“数据1”)
get(timeout) 返回队列中头部的一个数据并且删除该数据,如果队列为空,阻塞,也可以设置等待时间,时间到了没有获得,抛出异常 print(q.get())
get_nowait() 不等待得到队首数据,没有则抛出异常 print(q.get_nowait())
full() 判断队列是否满了 满True 不满False print(q.full())
empty() 判断队列是否为空,bool 空True 非空False print(q.empty())
qszie() 队列中的数据个数 print(q.qsize())
from queue import Queue


# 参数maxsize 是队列中云讯的最大项,如果省略参数,则无大小限制
# 返回值q 是队列对象
#
#
# put()方法,向队列存放数据,如果q为空,此方法阻塞,直到队列可用为止
# get()方法,返回队列中的一个数据,该数据被删除,如果队列为空,此方法阻塞
# get_nowait(),不等待,直接抛出异常
# full(),判断队列是否满了,如果队列对象已满,bool
# empty(),判断队列是否为空,bool 空True 非空False
# qszie(),队列中的数据个数

# 创建队列对象
q = Queue(maxsize=3) # 3 表示最多存放3个数据 q =
q.put("数据1")
print(q.empty())
q.put("数据2")
q.put("数据3")
print(q.full())
# q.put("数据4") # 程序阻塞
# print(q.get())
# print(q.get())
# print(q.get())
# print(q.get(timeout=3))# 等待3秒 取 没有数据抛出异常
# print(q.get_nowait())
print(q.qsize())

返回文章目录

七、生产者消费者模式

(一)什么是生产者消费者

  • 生产者:生产数据的线程
  • 消费者:消费数据的线程
  • 目的:平衡生产者和消费者的工作能力来提高程序的整体处理数据的速度

为什么使用生产者和消费者模式

  • 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当 中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理 完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必 须等待生产者。为了解决这个问题于是引入了生产者和消费者模式

什么是生产者消费者模式

  • 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼 此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力 基于队列实现生产者消费者模型

该模式配合队列使用

(二)生产者消费者实例说明

import threading
import queue
import time


# 生产者:厨师
def producer(name):
    count = 1
    while True:
        print("%s生产了包子%d" % (name, count))
        q.put(count) # 将包子存放到容器
        count +=1 # 循环一次 生产一次包子
        time.sleep(0.2) # 厨师生产速度
        print("包子总数",q.qsize())

# 消费者:顾客
def customer(name):
    while True:
        print("%s吃了包子%d"%(name,q.get()))
        time.sleep(1)# 消费者吃包子的速度

if __name__ == '__main__':
    # 创建队列容器
    q = queue.Queue(3) # 容器只能装下3个包子
    # 创建一个厨师A,两个顾客 B C,三个线程
    t1 = threading.Thread(target=producer,args=("厨师A",))
    t2 = threading.Thread(target=customer,args=("消费者A",))
    t3 = threading.Thread(target=customer,args=("消费者B",))

    # 启动线程
    t1.start()
    t2.start()
    t3.start()

可以看到,厨师生产的速度刚好满足,消费者的消费速度

在这里插入图片描述

在这里插入图片描述
通过异常处理,当商品供应不足的时候

"""
秒杀系统:底层原理模拟
"""
import threading
import time
from queue import Queue

# 中间仓库
goodses = Queue(10)

def produce():
    """商家"""
    while True:
        try:
            # 商家负责提供商品
            time.sleep(1)
            goodses.put_nowait("手机")
            print("商品已经调度一台,存放到商店")
        except Exception as e:
            print("仓库已满,商家等待中....")


def consumer():
    """客户"""
    while True:
        try:
            # 客户负责购买商品
            time.sleep(0.5)
            goods = goodses.get_nowait()
            print(f"购买到一个商品:{goods}")
        except Exception as e:
            print("正在排队,商品正在调度中,请等待....")


if __name__ == "__main__":
    # 商家
    p1 = threading.Thread(target=produce)
    p2 = threading.Thread(target=produce)
    p3 = threading.Thread(target=produce)
    p1.start()
    p2.start()
    # p3.start()

    # 用户
    c1 = threading.Thread(target=consumer)
    c2 = threading.Thread(target=consumer)
    c3 = threading.Thread(target=consumer)
    c1.start()
    c2.start()
    c3.start()

在这里插入图片描述
返回文章目录

八、threadLocal

这个东西究究竟是什么? 我们来层层观察递进思考

1、使用函数传参的方法

def process_student(num):
    std = Student(name)
    do_task_1(std)
    do_task_2(std)

def do_task_1(std):
    do_task_3(std)
    do_task_4(std)

def do_tasl_2(std):
    do_task_5(std)
    do_task_6(std)

每个函数一层一层调用都这么传参数那还得了?用全局变量?也不行,因为每个线程处理不 同的 Student 对象,不能共享

2、使用全局字典的方法
如果用一个全局 dict 存放所有的 Student 对象,然后以 thread 自身作为 key 获得线程对应的 Student 对象如何

import threading
global_dict = {}

def std_thread(num):
    std = Student(name)
    # 把std放到全局变量global_dict中
    gloabal_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():

    # 不传入std 而是根据当前线程查找
    std = global_dict[threading.current_thread()]
    pass

def do_task_2():

    # 任何函数都可以查找出当前线程的std变量
    std = global_dict[threading.current_thread()]

这种方式理论上是可行的,它最大的优点是消除了 std 对象在每层函数中的传递问题,但是, 每个函数获取 std 的代码有点 low 有没有更简单的方式

3、切入正题使用threadLocal的方法
threadLocal 应运而生,不用查找 dict,threadLocal 帮你自动做这件事

import threading

user = threading.local() # 全局变量,但是每个线程调用的时候互不影响

def print_message():
    user_name = user.name
    print(user_name,threading.current_thread().name)

def write_message(message):
    user.name = message # 自定义变量name假设是local的属性,给该属性赋值
    print_message() # 调用打印信息

if __name__ == '__main__':

    t1 = threading.Thread(target=write_message,args=("用户A",),name="Thread-A")
    t2 = threading.Thread(target=write_message,args=("用户B",),name="Thread-B")

    t1.start()
    t2.start()

    t1.join()
    t2.join()

如图下图结果:

在这里插入图片描述
全局变量userl就是一个ThreadLocal对象,每个线程对它都可以读写name属性,但互不影响。可以把user看成全局变量,但每个属性如user.name都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题。

九、全局解释锁

1、全局解释锁的定义
全局解释器锁[Global Interpreter Lock]是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程,常见的使用 GIL 的解释器有CPython与Ruby MRI。可以看到GIL并不是Python独有的特性,是解释型语言处理多线程问题的一种机制而非语言特性。

2、Python的解释器
在这里插入图片描述

详情参看全局解释锁文章作者:静海听风https://zhuanlan.zhihu.com/p/87307313
此处引用1和2

返回文章目录

十、Python 进程

- 进程:计算机中的一个独立执行的程序单元,一个软件可以包含多个进程。 - 术语:进程是计算机中最小的数据分配单元。 - 跟线程差不多 这里就不过多阐述
	import multiprocessing
	基本语法:
	def test():
	自定义函数
	# 创建一个独立的进程
	p1 = multiprocessing.Process(target=这个进程中要执行的函数) # 启动进程:固定语法,调用 start()启动进程
	p1.start()
"""
传统:多进程多任务
    multi 多个
    process 进程
    multiprocessing: python中用于处理多进程的一个模块
"""
import time
import multiprocessing


def eat():
    """吃饭"""
    for i in [1,2,3]:
        print("正在吃饭....")
        time.sleep(1)


def read():
    """阅读"""
    for i in [1,2]:
        print("看文章...")
        time.sleep(1)

#主进程的判断
if __name__ == "__main__":
    # 根据两个任务,创建两个进程
    eat_p1 = multiprocessing.Process(target=eat)
    read_p2 = multiprocessing.Process(target=read)
    # 启动两个进程
    eat_p1.start()
    read_p2.start()

在这里插入图片描述

返回文章目录


猜你喜欢

转载自blog.csdn.net/weixin_44238683/article/details/106134467
今日推荐