python_并发编程

目录

一、背景知识

1、为什么要有操作系统?

2、操作系统历史

1)第一代计算机(1940~1955):真空管和穿孔卡片(无操作系统)

2)第二代计算机(1955~1965):晶体管和批处理系统

3)第三代计算机(1965~1980):集成电路芯片和多道程序设计

4)第四代计算机(1980~至今):个人计算机

二、进程:最小资源单位

1、理解

2、进程与程序的区别

3、并发和并行

3.1 并发

3.2 并行

3.3 总结

4、multiprocessing模块

4.1 创建进程对象

4.2 join(timeout=None)和daemon

4.3 其他方法和属性

5、进程通信

1、进程队列Queue

2、管道Pipe

3、数据共享Manager

6、进程同步

7、进程池

8、协程

三、线程:最小执行单位

1、理解

2、线程的创建开销小

3、多线程应用举例

4、threading模块

4.1 创建线程对象

4.2 join()和setDaemon()

4.3 其他方法

4.5 多线程实现tcp通信

5、同步和异步

5.1 同步

5.2 异步

5.3 总结

6、Python的GIL全局解析器锁

7、互斥锁(同步锁)

 1、threading.Lock()对象

 2、threading.Condition对象

8、死锁(递归锁)

9、同步对象Event

10、信号量Semaphore

11、定时器

12、线程队列queue

1、class queue.Queue(maxsize=0):先进先出FIFO

2、class queue.LifoQueue(maxsize=0):后进先出LIFO

3、class queue.PriorityQueue(maxsize=0):存储数据时可设置优先级的队列

4、其他方法

13、生产者和消费者模式


一、背景知识

1、为什么要有操作系统?

        现代的计算机系统主要是由一个或者多个处理器(CPU),主存,硬盘,键盘鼠标,显示器,打印机,网络接口及其他输入输出设备组成。现代计算机系统是一个复杂的系统。
        其一:如果每位应用程序员都必须掌握该系统所有的细节那就不可能再编写代码了(严重影响了程序员的开发效率:全部掌握这些细节可能需要一万年....)
        其二:管理这些部件并加以优化使用,是一件极富挑战性的工作,于是,计算安装了一层软件(系统软件),称为操作系统。它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理以上所有设备。

总结:
        程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了它,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。

python_并发编程_第1张图片

2、操作系统历史

1)第一代计算机(1940~1955):真空管和穿孔卡片(无操作系统)

        第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage 失败之后一直到第二次世界大战,数字计算机的建造几乎没有什么进展,第二次世界大战刺激了有关计算机研究的爆炸性进展。

        lowa州立大学的john Atanasoff 教授和他的学生Clifford Berry建造了据认为是第-台可工作的数字计算机。该机器使用300个真空管。大约在同时Konrad Zuse在柏林用继电器构建了Z3计算机。英格兰布莱切利园的一个小组在1944年构建了Colossus Howard Aiken在哈佛大学建造了Mark 1,宾法尼亚大学的William Mauchley和他的学生JPresperEckert建造了ENIAC。这些机器有的是二进制的,有的使用真空管,有的是可编程的,但都非常原始,设置需要花费数秒钟时间才能完成最简单的运算。

        在这个时期,同一个小组里的工程师们,设计、建造、编程、操作及维护同一台机器,所有的程序设计是用纯粹的机器语言编写的,甚至更糟糕,需要通过成千上万根电缆接到插件板上连成电路来控制机器的基本功能。没有程序设计语言 (汇编也没有),操作系统则是从来都没听说过。使用机器的过程更加原始。

python_并发编程_第2张图片

工作过程:
        万能程序员将对应程序和数据已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序数据输入计算机内存,接着通过控制合启动程序针对数据运行,计算完毕,打印机输出计算结果,用户取走结果并卸下纸带(或卡片),才让下一个万能程序员上机。
优点:

        程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug 可以立刻处理)
缺点:
        浪费计算机资源,一个时间段内只有一个人用注意:同一时刻只有一个程序在内存中,被cpu调用执行,比方说10个程序的执行,是串行的。

2)第二代计算机(1955~1965):晶体管和批处理系统

第二代计算机的产生背景:
        由于当时的计算机非常昂贵,需要想办法减少机时的浪费。通常采用的方法就是批处理系统。
特点:
        设计人员、生产人员、操作人员、程序人员和维护人员直接有了明确的分工,计算机被锁在专用空调房间中,由专业操作人员运行,这便是大型机。
有了操作系统的概念
有了程序设计语言:FORTRAN语言或汇编语言,写到纸上,然后穿孔打成卡片,再将卡片盒带到输入室,交给操作员,然后喝着咖啡等待输出。

python_并发编程_第3张图片

一种早期的批处系统:

        a)程序员将卡片到1401处;

        b)1401机将批处理作业读到磁带上;
        c)操作员将输入带送至7094机;

        d)7094机进行计算;

        e)操作员将输出磁带送到1401机;

        f)1401机打印输出机;

        在收集了大约一个小时的批量作业之后,这些卡片被读进磁带,然后磁带被送到机房里并装到磁带机上。随后,操作员装入一个特殊的程序(现代操作系统的前身),它从磁带上读入第一个作业并运行其输出写到第二盘磁带上,而不打印。每个作业结束后,操作系统自动地从磁带上读入下一个作业并运行。当一批作业完全结束后,操作员取下输入和输出磁带,将输入磁带换成下一批作业,并把输出磁带拿到一台1401机器上进行脱机(不与主计算机联机)打印。
优点:批处理,节省了机时缺点:
        1. 整个流程需要人参与控制,将磁带搬来搬去(中间俩小人);
        2. 计算的过程仍然是顺序计算->串行;
        3. 程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率无法及时调试程序);

如何解决第一代的问题/缺点:
        1. 把一堆人的输入攒成一大波输入;
        2. 然后顺序计算(这是有问题的,但是第二代计算也没有解决);

        3. 把一堆人的输出攒成一大波输出;

3)第三代计算机(1965~1980):集成电路芯片和多道程序设计

如何解决第二代计算机的问题1:

        卡片被拿到机房后能够很快的将作业从卡片读入磁盘,于是任何时刻当一个作业结束时,操作系统就能将一个作业从磁带读出,装进空出来的内存区域运行,这种技术叫做同时的外部设备联机操作:SPOOLING 技术,该技术同时用于输出。当采用了这种技术后,不必将磁带搬来搬去了(中间俩小人不再需要)。
如何解决第二代计算机的问题2:
        第三代计算机的操作系统的关键技术:多道技术,cpu在执行一个任务的过程中,若需要操作硬盘,则发送操作硬盘的指令,指令一旦发出,硬盘上的机械手臂滑动读取数据到内存中,这一段时间,cpu 需要等待,时间可能很短,但对于cpu 来说已经很长很长,长到可以让cpu 做很多其他的任务,如果我们让 cpu 在这段时间内切换到去做其他的任务,这样cpu不就充分利用了吗。这正是多道技术产生的技术背景,I/0切换。
第三代计算机的操作系统仍然是批处理
        许多程序员开始怀念第一代独享的计算机,可以即时调试自己的程序。为了满足程序员们很快可以得到响应,出现了分时操作系统

如何解决第二代计算机的问题3:
分时操作系统:
多个联机终端 + 多道技术
        20个客户端同时加载到内存,有17在思考3个在运行,cpu就采用多道的方式处理内存中的这3个程序,由于客户提交的一般都是简短的指令而且很少有耗时长的,索引计算机能够为许多用户提供快速的交互式服务,所有的用户都以为自己独享了计算机资源。
题外知识补充:
        CTTS:麻省理工(MIT)在一合改装过的7094机上开发成功的,CTSS兼容分时系统第三代计算机广泛采用了必须的保护硬件(程序之间的内存彼此隔离)之后,分时系统才开始流行,MIT,贝尔实验室和通用电气在CTTS成功研制后决定开发能够同时支持上百终端的MULTICS(其设计者着眼于建造满足波士顿地区所有用户计算需求的一台机器)。
        后来一位参加过MULTICS研制的贝尔实验室计算机科学家Ken Thompson开发了一个简易的,单用户版本的MULTICS,这就是后来的UNIX系统。基于它衍生了很多其他的Unix版本,为了使程序能在任何版本的unix上运行,IEEE提出了一个unix标准即posix(可移植的操作系统接口Portable Operating System Interface)
        后来,在1987年,出现了一个UNIX的小型克隆,即minix用于教学使用。芬兰学生LinusTorvalds基于它编写了Linux。

4)第四代计算机(1980~至今):个人计算机

        随着大规模集成电路的发展,每平方厘米的硅片芯片上可以集成数千个晶体管,个人计算机时代就此到来。

二、进程:最小资源单位

1、理解

        假如有两个程序A和B,程序A执行到一半的过程中,需要读取大量的数据输入(I/0操作),而此时CPU只能静静地等待读取读取完数据才能继续执行,这样就白白浪费了CPU
资源。是不是在程序A读取的过程中,让程序B去执行,当程序A读取完数据之后,让程序B暂停,然后让程序A继续执行?

        当然没问题,但这里有一个关键词:切换(I/0切换或时间轮询切换)

        既然是切换,那么就涉及到了状态的保存,状态的恢复,加上程序A 与程序B所需要的系统资源(内存、硬盘、键盘等等)是不一样的,自然而然的就需要有一个东西去记得程序A和程序B分别需要什么资源,怎么去识别两个程序,所以就有了一个叫进程的抽象。

        即使可以利用的cpu只有一个(早期的计算机确实如此),也能保证支持 (伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。

举例(单核+多道,实现多个进程的并发执行):
        张三在一个时间段内有很多任务要做:写Python代码、看书的任务、交女朋友的任务、王者荣耀上分的任务...
        但同一时刻只能做一个任务 (cpu同一时间只能干一个活),如何才能玩出多个任务并发执行的效果?
        写一会代码再去跟某一个女孩聊聊天,再去打一会王者荣耀....这就保证了每个任务都在进行中。

2、进程与程序的区别

        程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。(抽象的概念)。程序是程序,进程是进程,程序不运行,永远不是进程。
进程定义:

        进程即程序(软件)在一个数据集上的一次动态执行过程。进程是对正在运行程序的一个抽象。进程一般由程序、数据集、进程控制块三部分组成。一个进程是一份独立的内存空间。多个进程之间用户程序无法互相操作。
程序: 我们编写的程序用来描述进程要完成哪些功能以及如何完成

数据集:是程序在执行过程中所需要使用的资源;
进程控制块:用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

举例:
        想象一位厨师正在为他的女儿烘制生日蛋糕,他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、韭菜,蒜泥等

在这个比喻中:

        做蛋糕的食谱就是程序(即用适当形式描述的算法),厨师就是处理器(cpu),而做蛋糕的各种原料就是输入数据,进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。

        现在假设厨师的儿子哭着跑了进来,说:头被蜜蜂蛰了,厨师想了想,处理儿子蛰伤的任务比给女儿做蛋糕的任务更重要,于是厨师就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册按照其中的指示处理蛰伤这里,我们看到处理器(cpu)从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位厨师又回来做蛋糕,从他离开时的那一步继续做下去。

        需要强调的是:同一个程序执行两次,那也是两个进程,比如打开Potplayer,虽然都是同个软件但是一个可以播放Pyhton,一个可以播放Java。

3、并发和并行

        无论是并行还是并发,在用户看来都是同时运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务。

3.1 并发

        是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发举例:
        你是一个cpu,你同时谈了三个女朋友,每一个都可以是一个恋爱任务,你被这三个任务共享,要玩出并发恋爱的效果,应该是你先跟女友1去看电影,看了一会说:不好,我要拉肚子,然后跑去跟第二个女友吃饭,吃了一会说:那啥,我去趟洗于间,然后跑去跟女友3喝下午茶。

3.2 并行

        同时运行,只有具备多个cpu才能实现并行,单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的)。

        有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了 cpu1,cpu2,cpu3,cpu4。可能任务1遇到I/0就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术,而一旦任务1的I/0结束了,操作系统会重新调用它(进程的调度、分配给哪个cpu运行由操作系统说了算),可能被分配给四个cpu中
的任意一个去执行。

3.3 总结

并发:系统具有       处理多个任务的能力
并行:系统具有同时处理多个任务的能力

并行一定是并发的,而并发不一定是并行的

python_并发编程_第4张图片

4、multiprocessing模块

4.1 创建进程对象

a. 通过Process造器创建

a)通过实例化一个Process()对象就是初始化一个进程

b)Process(target=func)对象中传入函数就是这个进程要处理的事情
c)调用Process()对象的start()就是开启一个新的进程,这个进程叫做子进程,原来程序执行的进程就是父进程
d)os.getpid()获取当前进程的进程号,进程号就是程序运行在CPU上唯一标记,就是进程的别名,方便系统对它进行操作

from multiprocessing import Process     # 进程对象
import time


def hi(num):
    print('hello %s' % num)
    time.sleep(3)
    print('ending...')


if __name__ == '__main__':
    """
        真正的并行,可在多核中随机选
    """
    t = Process(target=hi, args=(5,))  # 创建子进程对象(子线程)
    t1 = Process(target=hi, args=(4,))
    t.start()
    t1.start()
    print('我是主进程...')

"""
我是主进程...
hello 5
hello 4
ending...
ending...
"""

b. 继承multiprocessing.Process

from multiprocessing import Process
import time


class MyProcess(Process):  # ① Process
    def __init__(self, num):
        super().__init__()  # ②
        self.num = num

    def hi(self):
        print('hello %s' % self.num)
        time.sleep(3)

    def run(self):  # ③ 重写run方法
        """
        进程执行方法
        :return:
        """
        self.hi()


if __name__ == '__main__':
    t1 = MyProcess(5)  # 创建子进程
    t1.start()  # 通知run方法开始执行

    t2 = MyProcess(4)  # 创建子进程
    t2.start()

    print('我是主进程...')

"""
我是主进程...
hello 5
hello 4
"""

4.2 join(timeout=None)和daemon

join(timeout=None):timeout默认单位是秒

from multiprocessing import Process
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    t1 = Process(target=listen)
    t1.start()

    t2 = Process(target=game)
    t2.start()

    # 在子进程运行结束前,这个子进程的主进程将一直被阻塞
    t1.join()
    t2.join()

    print('我是主进程...')


"""
begin to listen 15:53:03
begin to game 15:53:03
end to listen 15:53:06
end to game 15:53:08
我是主进程...
"""

 daemon属性:

from multiprocessing import Process
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    """
    进程的创建开销很大,主进程都执行完了,俩子进程还没开起来
    """
    t1 = Process(target=listen)
    t1.daemon = True  # 将该子进程设置为守护进程,必须在start之前
    t1.start()

    t2 = Process(target=game)
    t2.daemon = True  # 将该子进程设置为守护进程,必须在start之前
    t2.start()

    print('我是主进程...')

"""
我是主进程...
"""

4.3 其他方法和属性

multiprocessing.Process 实例的方法
        run():用于表示进程活动的方式

        start():启动进程活动(让进程处于就绪的状态,等待CPU去执行调用)

        is_alive():返回进程是否活动的

        terminate():不管任务是否完成,立即停止工作进程
属性
        daemon:守护进程和线程的setDaemon()功能一样

        name:进程名称

        pid:进程号

from multiprocessing import Process
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    """
    进程的创建开销很大,主进程都执行完了,俩子进程还没开起来
    """
    t1 = Process(target=listen)
    t1.daemon = True  # 将该子进程设置为守护进程,必须在start之前
    t1.start()

    # 其他方法
    print(t1.is_alive())    # # 返回进程是否活动的

    t2 = Process(target=game)
    # t2.daemon = True  # 将该子进程设置为守护进程,必须在start之前
    t2.start()
    time.sleep(2)
    t2.terminate()  # 不管任务是否完成,立即停止工作进程
    print(t2.name)  # 进程名称
    print(t2.pid)   # 进程号
    print('我是主进程...')

"""
True
begin to listen 16:08:58
begin to game 16:08:58
Process-2
17784
我是主进程...
"""

5、进程通信

1、进程队列Queue

import time
import multiprocessing


def foo(q):
    time.sleep(1)
    print('son process')
    # 子进程put
    q.put(123)
    q.put('sxt')


if __name__ == '__main__':
    q = multiprocessing.Queue()     # 进程队列对象
    p = multiprocessing.Process(target=foo, args=(q,))  # 创建子进程执行foo函数
    p.start()

    print('main process')
    # 主进程get
    print(q.get())
    print(q.get())

2、管道Pipe

"""
创建双向管道实现进程间通信
"""
from multiprocessing import Process, Pipe


def consumer(p, name):
    """

    :param p:
    :param name:
    :return:
    """
    while True:
        try:
            baozi = p.recv()
            print('%s收到包子:%s' % (name, baozi))
        except EOFError:
            p.close()
            break


def producer(seq, p):
    """
    
    :param seq: 
    :param p: 
    :return: 
    """
    for i in seq:
        p.send(i)
    else:
        p.close()


if __name__ == '__main__':
    parent_conn, child_conn = Pipe()    # 双向管道对象,分别对应两个端,分别可以发和收

    # 子进程执行consumer
    c1 = Process(target=consumer, args=(child_conn, 'c1'))
    c1.start()

    # 主进程执行producer
    producer([i for i in range(10)], parent_conn)

    parent_conn.close()
    child_conn.close()

    c1.join()
    print('主进程...')

3、数据共享Manager

"""
Manager:实现进程之间传递字典 列表等
"""
from multiprocessing import Manager, Process


def f(d, l, n):
    d[n] = '1'
    d['2'] = 2
    d[0.25] = None
    l.append(n)
    print()
    print(d)
    print(l)
    print(n)
    print('son process...')


if __name__ == '__main__':
    manager = Manager()

    dic = manager.dict()      # 创建进程通信字典
    lis = manager.list(range(5))  # 创建进程通信列表

    p_list = []
    for i in range(5):
        p = Process(target=f, args=(dic, lis, i))   # 开启子进程执行f
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()

    print(dic)
    print(lis)
    print('main process...')

6、进程同步

from multiprocessing import Process, Lock
import time


def f(lock_l, i):
    lock_l.acquire()    # 加锁
    time.sleep(1)
    print('hello world %s' % i)
    lock_l.release()    # 释放锁


if __name__ == '__main__':
    # 创建锁对象
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

"""
hello world 0
hello world 1
hello world 3
hello world 2
hello world 5
hello world 6
hello world 4
hello world 7
hello world 8
hello world 9
"""

7、进程池

from multiprocessing import Process, Pool
import time, os


def foo(i):
    time.sleep(2)
    print(i)
    print('son', os.getpid())  # 获取子进程pid
    return 'HELLO %s' % i


def bar(args):
    """
    主进程的回调函数
    :param args: 子进程foo的返回值
    :return:
    """
    print(args)
    # print('Bar:', os.getpid())
    # print('hello')


if __name__ == '__main__':
    pool = Pool(5)  # 创建一个进程池,最大进程数是5,默认值是电脑CPU核数
    print('maim', os.getpid())  # 获取主进程pid

    for i in range(1, 101):
        # pool.apply(func=foo, args=(i, ))    # 同步
        """
            callback 回调函数就是某个动作或者函数执行成功后再去执行的函数,是主进程内的函数
        """
        pool.apply_async(func=foo, args=(i,), callback=bar)  # 异步
    # join与close调用顺序是固定的,否则会报错
    pool.close()
    pool.join()

    print('ending...')

8、协程

        yield实现协程思想:

"""
yield实现协程思想(协程的底层就是通过yield实现的)
"""
import time


def consumer(name):
    """
    消费者
    :param name:
    :return:
    """
    print("准备吃包子...")

    while True:
        # 接收send发来的数据
        _baozi = yield
        print("[%s] 吃掉了包子 %s" % (name, _baozi))
        # time.sleep(1)


def producer():
    """
    生产者
    :return:
    """
    # 两种写法效果一致
    r = con.__next__()
    r = next(con2)
    n = 0
    while 1:
        time.sleep(1)
        print("\033[32;1m[producer]\033[0m 制作了包子 %s and %s" % (n, n + 1))
        # 发送给yield数据
        con.send(n)
        con2.send(n + 1)
        n += 2


if __name__ == '__main__':
    con = consumer('c1')
    con2 = consumer('c2')
    producer()

 结果:

python_并发编程_第5张图片

         greenlet模块:

from greenlet import greenlet


def test1():
    print(12)
    gr2.switch()    # 类似yield
    print(34)


def test2():
    print(56)
    gr1.switch()
    print(78)
    gr1.switch()


if __name__ == '__main__':
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr2.switch()  # 类似yield

"""
56
12
78
34
"""

         gevent模块:

import gevent
import requests, time

start = time.time()


def f(url):
    """
    从指定的网站读取字节数据
    :param url:
    :return:
    """
    print('GET:%s' % url)
    resp = requests.get(url)
    data = resp.text
    print('%d bytes received from %s.' % (len(data), url))


# 相当于开始4个进程去跑
gevent.joinall([
    gevent.spawn(f, 'https://www.python.org/'),
    gevent.spawn(f, 'https://www.yahoo.com/'),
    gevent.spawn(f, 'https://www.baidu.com/'),
    gevent.spawn(f, 'https://www.sina.com.cn/')
])

print('time:', time.time() - start)

三、线程:最小执行单位

1、理解

        进程只是用来把资源集中到一起 (进程只是一个资源单位,或者说资源集合),而线程才是 cpu 上的执行单位。在操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程(主线程)。多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程多个控制线程共享该进程的地址空间。线程不能够独立执行,必须依存在进程中。一个线程可以创建和撤销另一个线程(子线程),同一个进程中的多个线程之间可以并发执行。
举例:一条流水线工作的过程
        一条流水线(线程)必须属于一个车间,一个车间的工作过程是一个进程车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线,流水线的工作需要电源,电源就相当于cpu,多线程相当于一个车间内有多条流水线,都共用一个车间的资源。

2、线程的创建开销小

创建进程的开销要远大于线程?
        如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu),一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程),创建一个进程就是创建一个车间(申请空间在该空间内建至少一条流水线)。而创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小。
进程之间是竞争关系,线程之间是协作关系?
        车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,腾讯电脑管家把木马进程当做病毒干死)一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,腾讯内的线程是合作关系,不会自己干自己)。

3、多线程应用举例

        开启一个文本处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字显示,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时不能处理文字和自动保存,自动保存时又不能输入和处理文字。

4、threading模块

4.1 创建线程对象

方式一:通过Thread(target=test)构造器创建对象,target 为要执行的方法

Thread(group=None,target=None,name=None,args=(),kwargs={})

group:线程组,目前还没有实现,库引用中提示必须是None;

target:要执行的方法

name:线程名;
args/kwargs:要传入方法的参数

​

"""
 切换有两种:IO阻塞切换和时间轮询切换
"""
import threading


def hi(num):
    print('hello %s' % num)


if __name__ == '__main__':
    """
        串行实现了并发的效果
    """
    t = threading.Thread(target=hi, args=(5,))  # 创建线程对象(子线程)
    t1 = threading.Thread(target=hi, args=(4,))
    t.start()
    t1.start()
    print('我是主线程...')

[点击并拖拽以移动]
​

 方式二:继承threading.Thread

import threading


class MyThread(threading.Thread):  # ① 继承threading.Thread
    def __init__(self, num):
        threading.Thread.__init__(self)  # ② 调用threading.Thread.__init__(self)
        self.num = num

    def hi(self):
        print('hello %s' % self.num)

    def run(self):  # ③ 重写run方法
        """
        线程执行方法
        :return: 
        """
        self.hi()


if __name__ == '__main__':
    t1 = MyThread(5)
    t1.start()  # 通知run方法开始执行

    t2 = MyThread(4)
    t2.start()

    print('我是主线程...')

4.2 join()和setDaemon()

join():在子线程运行结束前,这个子线程的主线程将一直被阻塞

import threading
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    t1 = threading.Thread(target=listen)
    t1.start()

    t2 = threading.Thread(target=game)
    t2.start()
    
    # 在子线程运行结束前,这个子线程的主线程将一直被阻塞
    t1.join()
    t2.join()

    print('我是主线程...')

setDaemon(True|False):守护线程

        将线程声明为守护线程,必须在start()方法调用之前设置,这个方法基本和join()是相反的。当我们在程序运行时,执行一个主线程,如果主线程又创建了一个子线程,主线程和子线程就兵分两路分别运行,当主线程完成想退出时,会检查子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出,但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon()方法

import threading
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    t1 = threading.Thread(target=listen)
    t1.setDaemon(True)
    t1.start()

    t2 = threading.Thread(target=game)
    t2.setDaemon(True)  # 将该子线程设置为守护线程,必须在start之前
    t2.start()

    print('我是主线程...')

"""
begin to listen 21:22:32
begin to game我是主线程...
"""

4.3 其他方法

"""
❀ threading.Thread 实例的方法:
    run():用于表示线程活动的方式
    start():启动线程活动(让线程处于就绪的状态,等待CPU去执行调用)
    isAlive():返回线程是否活动的
    getName():返回线程名
    setName():设置线程名
❀ threading模块提供的一些方法:
    threading.current_thread()|threading.currentThread():返回当前线对象
    threading.main_thread():返回主线程对象
    threading.enumerate():返回一个包含正在运行的线程的list,正在运行指线程启动后、结束前,不包含启动前和终止后的线程
    threading.active_count()|threading.activeCount():返回正在运行的线程数量
    threading.get_ident():返回线程ID,非0整数
"""
import threading
import time


def listen():
    print('begin to listen', time.strftime('%X'))
    time.sleep(3)
    print('end to listen', time.strftime('%X'))


def game():
    print('begin to game', time.strftime('%X'))
    time.sleep(5)
    print('end to game', time.strftime('%X'))


if __name__ == '__main__':
    t1 = threading.Thread(target=listen)
    t1.start()
    # 查看线程是否活动
    print(t1.is_alive())  # True
    # 设置线程名
    t1.setName('我是子线程---听音乐')
    # 获取线程名
    print(t1.getName())  # 我是子线程---听音乐

    # 返回当前线程
    current_t = threading.current_thread()
    print('当前线程:', current_t)

    t2 = threading.Thread(target=game)
    t2.start()

    print(threading.enumerate())  # [<_MainThread(MainThread, started 13436)>, , ]
    print(threading.active_count())  # 3
    print('我是主线程...')

    print(threading.get_ident())  # 14456

"""
begin to listenTrue
我是子线程---听音乐
当前线程:  <_MainThread(MainThread, started 14456)>
21:44:51
begin to game[<_MainThread(MainThread, started 14456)>, , ]
3
 21:44:51
我是主线程...
14456
end to listen 21:44:54
end to game 21:44:56

"""

4.5 多线程实现tcp通信

实现服务端同时和多个客户端发消息

tcp_server.py:

import socket
import threading

tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(('127.0.0.1', 8000))
tcp_server.listen(5)


def server(conn):
    while True:
        data = conn.recv(1024)
        if not data.strip():
            break
        if 'exit' == data.decode('utf-8'):
            conn.send(data)
            break
        print('客户端说:', data.decode('utf-8'))
        msg = input(">>:")
        conn.send(msg.encode('utf-8'))


if __name__ == '__main__':
    while True:
        conn, _ = tcp_server.accept()
        s = threading.Thread(target=server, args=(conn, ))
        s.start()

tcp_client.py:

import socket

# 建立socket连接(AF_INET:基于网络套接字家族,SOCK_STREAM:tcp协议)
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 买手机
# 与服务端建立连接
tcp_client.connect(('127.0.0.1', 8000))  # 拨号
while True:
    msg = input(">>:")
    if not msg.strip():
        continue
    # 发送消息给服务器
    tcp_client.send(msg.encode('utf-8'))  # 说话
    # 接收服务端的信息
    data = tcp_client.recv(1024)  # 听话
    if 'exit' == data.decode('utf-8'):
        print('与服务器断开连接,如需发送请重连')
        break
    print("服务端说:", data.decode('utf-8'))
# 断开与服务器的连接
tcp_client.close()  # 挂电话

 练习:

import threading

r_l = []  # 用户输入的数据
f_l = []  # 格式化后的数据


def _talk():
    """
    读取
    :return:
    """
    while True:
        msg = input('>>:').strip()
        r_l.append(msg)


def _format():
    """
    格式化
    :return:
    """
    while True:
        if r_l:
            data = r_l.pop()    # 弹出列表后面的数据
            f_l.append(data.upper())


def _save():
    """
    保存
    :return:
    """
    while True:
        if f_l:
            data = f_l.pop()
            with open('./test.txt', 'a+') as f:
                f.write(data+'\n')


if __name__ == '__main__':
    t1 = threading.Thread(target=_talk)
    t2 = threading.Thread(target=_format)
    t3 = threading.Thread(target=_save)
    t1.start()
    t2.start()
    t3.start()

5、同步和异步

5.1 同步

        同步的思想是:所有的操作都做完,才返回给用户。这样用户在线等待的时间太长,给用户一种卡死了的感觉(就是系统迁移中点击了迁移界面就不动了,但是程序还在执行卡死了的感觉)。这种情况下,用户不能关闭界面,如果关闭了,即迁移程序就中断了。

5.2 异步

        将用户请求放入消息队列,并反馈给用户友好提示,系统迁移程序启动,你可以关闭浏览器了。然后程序再慢慢地去写入数据库去。这就是异步。用户没有卡死的感觉,会告诉你你的请求系统已经响应了。你可以关闭界面了。

5.3 总结

同步,是所有的操作都做完,才返回给用户结果。即写完数据库之后,再响应用户,用户体验感不好。

异步,不用等所有操作等做完,就响应用户请求。即先响应用户请求,然后慢慢去写数
据库,用户体验较好。
异步操作例子:
        为了避免短时间大量的数据库操作就使用缓存机制,也就是消息队列。先将数据放入消息队列,然后再曼慢写入数据库。
        引入消息队列机制,虽然可以保证用户请求的快速响应,但是并没有使得我数据迁移的时间变短(即80万条数据写入mysql需要1个小时用了缓存机制之后还是需要1个小时,只是保证用户的请求的快速响应。用户输入完httpurl请求之后,就可以把浏览器关闭了,干别的去了。如果不用缓存机制,浏览器不能关闭)。

同步实例: 银行的转账功能、打电话等

6、Python的GIL全局解析器锁

        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 notthread-safe. (However, since the GIL exists,other features have grown to depend on the guarantees that it enforces.)
        上面的意思就是:在Cpython 解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。无论你启动多少个线程,你有多少个CPU,Python在执行的时候会在同一时刻只允许一个进程的一个线程运行。

对于IO密集型的任务:Python的多线程是有意义的。

对于计算密集型的任务:Python的多线程就不推荐,可以采用多进程+协程

python_并发编程_第6张图片

python_并发编程_第7张图片

         计算密集不推荐使用Python多线程,因为没有效率,使用多线程还不如直接调用,因为有这个全局解析器锁的存在,虽然是多线程,但同一时刻它只会在多核中随机选一个去跑,然后计算密集的切换是时间轮询,那么任务就会不停的被打断去做别的线程任务,切换的时候状态的保存和恢复是耗时的,所以整体时间上就会变得更长。

import threading
import time


def add():
    sum = 0
    for i in range(10000000):
        sum += i
    print('add:', sum)


def mul():
    res = 1
    for i in range(1, 100000):
        res *= i
    print('mul:', res)


if __name__ == '__main__':
    print('start...')

    start = time.time()
    add()
    mul()
    # t1 = threading.Thread(target=add)
    # t2 = threading.Thread(target=mul)
    # lis = [t1, t2]
    # for t in lis:
    #     t.start()
    # for t in lis:
    #     t.join()

    print('time: %s' % (time.time() - start))
    print('ending...')

 执行结果:

python_并发编程_第8张图片

7、互斥锁(同步锁)

        锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire 方法来获取锁对象(如果其它线程已经获得了该锁则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

 1、threading.Lock()对象

acquire(blocking=True,[timeout]):使线程进入同步阻塞状态尝试获得锁定。

release():释放锁,使用前线程必须已获得锁定,否则将抛出异常。

 多个线程同时操作num,导致数据不安全:

"""
100个线程start后开始执行,每个线程在①时还没有一个线程执行到②,所以拿到的num都是0,等待1秒后开始执行②,也就是在0的基础上加1,所以最终结果是1
"""
import threading
import time


def add():
    """
    先拿到数据等1秒后再加1
    :return:
    """
    global num
    tmp = num  # ①
    time.sleep(1)
    num = tmp + 1  # ②


if __name__ == '__main__':
    num = 0
    lis = []
    for i in range(100):
        t = threading.Thread(target=add)
        t.start()
        lis.append(t)
    for t in lis:
        t.join()

    print(num)


"""
结果:
1
"""

 缩小等待时间,时间越小越接近准确值:

"""
100个线程start后开始执行,每个线程在①时还没有一个线程执行到②,所以拿到的num都是0,等待1秒后开始执行②,也就是在0的基础上加1,所以最终结果是1
"""
import threading
import time


def add():
    """
    先拿到数据等1秒后再加1
    :return:
    """
    global num
    tmp = num  # ①
    time.sleep(0.0000000001)
    num = tmp + 1  # ②


if __name__ == '__main__':
    num = 0
    lis = []
    for i in range(100):
        t = threading.Thread(target=add)
        t.start()
        lis.append(t)
    for t in lis:
        t.join()

    print(num)


"""
结果:
2
"""

 解决办法:加锁

"""
添加互斥锁,结果肯定为100,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
"""
import threading
import time


def add():
    """
    先拿到数据等0.1秒后再加1
    :return:
    """
    global num
    _lock.acquire()     # 加锁
    tmp = num
    time.sleep(0.1)
    num = tmp + 1
    _lock.release()     # 释放锁


if __name__ == '__main__':
    # 创建互斥锁对象
    _lock = threading.Lock()

    num = 0
    lis = []
    for i in range(100):
        t = threading.Thread(target=add)
        t.start()
        lis.append(t)
    for t in lis:
        t.join()

    print(num)

"""
100
"""

分析:

1. 100个线程去抢GIL锁,即抢执行权限
2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞被迫交出执行权限,即释放GIL

4. 直到线程1重新抢到GIL,开始从上次暂停的位置继续执行、直到正常释放互斥锁
lock,然后其他的线程再重复234的过程。

 2、threading.Condition对象

        Condition 除了具有 Lock 对象的acquire 方法和release 方法外,还有 wait(线程等待)、notify(通知其他某个线程)、notifyAll(通知其他所有线程)方法等用于条件处理。Condition对象可以在某些事件触发或者达到特定条件后才处理数据。

 一边加1,一边减1:

import threading
import time


def add():
    global num1
    tmp = num1
    time.sleep(0.01)
    num1 = tmp + 1
    print('num1:', num1)


def sub():
    global num2
    tmp = num2
    time.sleep(0.01)
    num2 = tmp - 1
    print('num2:', num2)


if __name__ == '__main__':
    num1 = 0
    num2 = 100

    # 一边加法一边减法

    for i in range(100):
        t1 = threading.Thread(target=add)
        t2 = threading.Thread(target=sub)
        t1.start()
        t2.start()

    time.sleep(2)

结果:

python_并发编程_第9张图片

 解决方式:加锁

import threading
import time


def add():
    global num1
    _lock.acquire()  # 加锁
    tmp = num1
    time.sleep(0.01)
    num1 = tmp + 1
    print('num1:', num1)
    _lock.release()  # 释放锁


def sub():
    global num2
    _lock.acquire()
    tmp = num2
    time.sleep(0.01)
    num2 = tmp - 1
    print('num2:', num2)
    _lock.release()


if __name__ == '__main__':
    _lock = threading.Condition()
    num1 = 0
    num2 = 100

    # 一边加法一边减法

    for i in range(100):
        t1 = threading.Thread(target=add)
        t2 = threading.Thread(target=sub)
        t1.start()
        t2.start()

    time.sleep(5)
    print('---------')
    print(num1, num2)

 结果:

python_并发编程_第10张图片

练习:使用threading.Condition实现红绿灯人走车停,人停车走的效果

import threading
import time


class Street(object):
    def __init__(self, condition):
        self.flag = False  # True:绿灯,False:红灯
        self.condition = condition  # 街道拥有condition的能力

    def south_to_north(self):
        """
        绿灯
        :return:
        """
        while True:
            self.condition.acquire()
            # 人停
            if not self.flag:
                self.condition.wait()
            # 人走
            print('人[%s]走南北向...' % threading.current_thread().name)
            time.sleep(2)
            # 变灯
            self.flag = not self.flag
            # 通知车走
            self.condition.notify()
            self.condition.release()

    def east_to_west(self):
        """
        红灯
        :return:
        """
        while True:
            self.condition.acquire()
            # 车停
            if self.flag:
                self.condition.wait()
            # 车走
            print('车[%s]走东西向...' % threading.current_thread().name)
            time.sleep(1)
            # 变\
            self.flag = not self.flag
            # 通知人走
            self.condition.notify()
            self.condition.release()


class Person(threading.Thread):
    def __init__(self, street, name):
        super().__init__(name=name)  # 修改线程名称
        self.street = street

    def run(self):
        self.street.south_to_north()  # 人开始走


class Car(threading.Thread):
    def __init__(self, street, name):
        super().__init__(name=name)  # 修改线程名称
        self.street = street

    def run(self):
        self.street.east_to_west()  # 车开始走


if __name__ == '__main__':
    s = Street(threading.Condition())
    p = Person(s, '姬如千泷')
    p.start()

    c = Car(s, '小轿车')
    c.start()

8、死锁(递归锁)

        所谓死锁:是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。 由于资源占用是互斥的,当某个线程提出申请资源后,使得有关线程在无外力协助下,永远分配不到必需的资源而无法继续运行这就产生了一种特殊现象死锁。
        所以,在设计程序时就应该避免双方相互持有对方的锁的情况

"""
第一个线程先拿到A锁(此时第二个线程也想要A,但只能等待第一个线程将A释放),4秒后又拿到了B锁,1秒后将AB锁先后释放,接下来,
线程1来到了②拿到了B锁,4秒后线程1拥有B锁需要A锁
线程2来到了①拿到了A锁,4秒后线程2拥有A需要B锁
导致死锁
"""
import threading
import time


class MyThread(threading.Thread):
    def actionA(self):
        lock_A.acquire()
        print(self.name, '拿到A锁', time.strftime('%X'))
        time.sleep(4)

        lock_B.acquire()
        print(self.name, '拿到了A,也拿到了B锁', time.strftime('%X'))
        time.sleep(1)
        lock_B.release()
        print(self.name, '拥有AB,释放B', time.strftime('%X'))
        lock_A.release()
        print(self.name, '拥有A,释放A', time.strftime('%X'))

    def actionB(self):
        lock_B.acquire()
        print(self.name, '拿到B锁', time.strftime('%X'))
        time.sleep(4)

        lock_A.acquire()
        print(self.name, '拿到了B,也拿到了A锁', time.strftime('%X'))
        time.sleep(1)
        lock_A.release()
        print(self.name, '拥有AB,释放A', time.strftime('%X'))
        lock_B.release()
        print(self.name, '拥有B,释放B', time.strftime('%X'))

    def run(self):
        self.actionA()  # ①
        self.actionB()  # ②


if __name__ == '__main__':
    lock_A = threading.Lock()
    lock_B = threading.Lock()
    lis = []
    for i in range(5):
        t = MyThread()
        t.start()
        lis.append(t)
    for t in lis:
        t.join()

    print('ending...')


"""
Thread-1 拿到A锁 18:26:36
Thread-1 拿到了A,也拿到了B锁 18:26:40
Thread-1 拥有AB,释放B 18:26:41
Thread-1 拥有A,释放A Thread-218:26:41
Thread-1 拿到B锁 18:26:41
 拿到A锁 18:26:41
"""

 使用threading.RLock()避免死锁:

"""
使用一个锁对象threading.RLock(),底层有一个计数器,计数器大于0时,就不会去抢夺资源
"""
import threading
import time


class MyThread(threading.Thread):
    def actionA(self):
        lock.acquire()
        print(self.name, '拿到A锁', time.strftime('%X'))
        time.sleep(4)

        lock.acquire()
        print(self.name, '拿到了A,也拿到了B锁', time.strftime('%X'))
        time.sleep(1)
        lock.release()
        print(self.name, '拥有AB,释放B', time.strftime('%X'))
        lock.release()
        print(self.name, '拥有A,释放A', time.strftime('%X'))

    def actionB(self):
        lock.acquire()
        print(self.name, '拿到B锁', time.strftime('%X'))
        time.sleep(4)

        lock.acquire()
        print(self.name, '拿到了B,也拿到了A锁', time.strftime('%X'))
        time.sleep(1)
        lock.release()
        print(self.name, '拥有AB,释放A', time.strftime('%X'))
        lock.release()
        print(self.name, '拥有B,释放B', time.strftime('%X'))

    def run(self):
        self.actionA()
        self.actionB()


if __name__ == '__main__':
    lock = threading.RLock()
    lis = []
    for i in range(5):
        t = MyThread()
        t.start()
        lis.append(t)
    for t in lis:
        t.join()

    print('ending...')

9、同步对象Event

        线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。

        为了解决这些问题,我们需要使用threading库中的Event 对象。对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event 对象中的信号标志被设置为假。如果有线程等待一个Event 对象,而这个Event 对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个 Event 对象的信号标志设置为真,它将唤醒所有等待这个Event 对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件,继续执行。


event.isSet():返回event的状态值;

event.wait():如果event.isSet()== False将阻塞线程;

event.set():设置event的状态值为True,所有阻塞池的线程激活进入就绪状态等待操作系统调度;
event.clear():恢复event 的状态值为False;

import threading
import time


class Boss(threading.Thread):
    def run(self):
        print('今天加班到10点')
        _event.set()  # 将等待线程的False状态修改为True

        time.sleep(3)
        print('22:00到了,下班...')
        _event.set()


class Worker(threading.Thread):
    def run(self):
        print(_event.is_set())
        _event.wait()  # 默认值是False
        print(_event.is_set())
        print('唉,命苦!!!')
        _event.clear()  # 清除设置过的状态,恢复默认值False
        _event.wait()
        print('happy...')


if __name__ == '__main__':
    _event = threading.Event()
    t1 = Boss()
    t2 = Worker()
    t2.start()
    t1.start()


"""
False
今天加班到10点
True
唉,命苦!!!
22:00到了,下班...
happy...
"""

10、信号量Semaphore

        在同步锁下,每次可以几条线程进行操作

import threading
import time


class MyThread(threading.Thread):
    def run(self):
        if _lock.acquire():     # 上锁了就是True
            print(self.name)
            time.sleep(2)
            _lock.release()


if __name__ == '__main__':
    _lock = threading.Semaphore(5)  # 设置信号量对象
    lis = []
    for i in range(100):
        t = MyThread()
        t.start()
        lis.append(t)

11、定时器

        定时器,指定秒后执行某操作

from threading import Timer


def hello():
    print('hello world!')


t = Timer(2, hello)     # 2秒后执行hello函数
t.start()

定时器生成验证码:

"""
生成4位随机验证码,且进行验证
"""
from threading import Timer
import random


class Code(object):
    def __init__(self, second):
        self.make_cache(second)

    def make_cache(self, second=5):
        """
        加载生成验证码方法(5秒后验证码失效,重新生成验证码)
        :return:
        """
        self.cache = self.make_code()  # 调用生成验证码赋值给cache
        print(self.cache)  # 打印验证码
        self.t1 = Timer(second, self.make_cache)
        self.t1.start()

    def make_cache2(self):
        """
        如果输入错误验证码,立即重新生成
        :return:
        """
        self.cache = self.make_code()
        print(self.cache)

    def make_code(self):
        """
        生成验证码方法
        :return:
        """
        res = ''
        for i in range(4):
            s1 = str(random.randint(0, 9))  # 生成0-9格式随机数
            s2 = chr(random.randint(65, 90))  # 生成A-Z随机字母
            s3 = chr(random.randint(97, 122))  # 生成A-Z随机字母
            res += random.choice([s1, s2, s3])  # 随机返回序列中任意一个
        return res

    def check(self):
        """
        验证方法
        :return:
        """
        while True:
            inp = input('>>:').strip()
            if inp.upper() == self.cache.upper():
                print('验证成功')
                self.t1.cancel()  # 取消定时器执行
                break
            else:
                self.make_cache2()  # 重新调用立刻生成


if __name__ == '__main__':
    c = Code(30)
    c.check()

12、线程队列queue

1、class queue.Queue(maxsize=0):先进先出FIFO

"""
get和put都会阻塞
put会因为塞满了塞不进去
get会因为取空了取不出来
"""
import queue

q = queue.Queue()    #  maxsize 小于等于零,队列尺寸为无限大
q.put('first')
q.put('second')
q.put('third')

while True:
    print(q.get())
    print(q.get())
    print(q.get())

结果:

python_并发编程_第11张图片

2、class queue.LifoQueue(maxsize=0):后进先出LIFO

"""
get和put都会阻塞
put会因为塞满了塞不进去
get会因为取空了取不出来
"""
import queue

q = queue.LifoQueue()    #  maxsize 小于等于零,队列尺寸为无限大
q.put('first')
q.put('second')
q.put('third')

while True:
    print(q.get())
    print(q.get())
    print(q.get())

 结果:

python_并发编程_第12张图片

3、class queue.PriorityQueue(maxsize=0):存储数据时可设置优先级的队列

"""
get和put都会阻塞
put会因为塞满了塞不进去
get会因为取空了取不出来
"""
import queue

q = queue.PriorityQueue()    #  maxsize 小于等于零,队列尺寸为无限大
# put进入一个元组或者列表,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((2,'星魂'))
q.put((1, '云中君'))
q.put((3, '东皇太一'))

print(q.get())
print(q.get())
print(q.get())
print(q.get())

"""
(1, '云中君')
(2, '星魂')
(3, '东皇太一')
"""

结果:

python_并发编程_第13张图片

4、其他方法

queue --- 一个同步的队列类 — Python 3.7.13 文档

"""
get和put都会阻塞
put会因为塞满了塞不进去
get会因为取空了取不出来
"""
import queue

q = queue.PriorityQueue(3)
# put进入一个元组或者列表,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((2, '星魂'))
q.put((1, '云中君'))
q.put((3, '东皇太一'))
# q.put((4, '姬如千泷'), block=False)  # queue.Full,block默认是True(满了阻塞不报错,想要报错改成False)

# 下面的方法三个队列对象都拥有
print(q.qsize())  # 返回队列大小
print(q.full())  # 队列是否满,满了True,反之,False
print(q.empty())  # 队列是否为空,空了True,反之,False

print(q.get())
print(q.get())
print(q.get())

# 设置False如果空了,报错:_queue.Empty
# print(q.get(block=False))   # 相当于q.get_nowait()

13、生产者和消费者模式

        生产者/消费者模式的产生主要目的就是为了解决非同步的生产与消费之间的问题。
        什么是非同步呢?
        比如我刚刚生产了某个产品,而此时你正在打游戏,没空来取,要打完游戏来取,这就导致了我生产产品和你取产品是两个非同步的动作,你不知道我什么时候生产完产品,而我也不知道你什么时候来取。
        而生产者/消费者模式就是解决这个非同步问题的,因为肯定不可能我生产完一个就给你打个电话叫你来取,然后等你取完我再生产下一个,这是多么低效的一种做法。所以这个模式运用而生,这个模式在生活中也有很好的体现,如:快递员派信这个例子,我就是生产者,快递员就是消费者,而生产者与消费者之间是通过什么来解决这种非同步的问题呢?就是一个存储中介,作为快递员派信这个例子中,信箱就是这个存储中介,每次我只要把写完的信扔入信箱,而快递员隔三差五的就会来取一次信,这两个动作是完全异步的,我把信扔入信箱后就不需要管什么了,之后肯定有快递员来取。

import queue
import threading
import time

q = queue.Queue()


def producer(name='A厨师', num=5):
    """
    生产者
    :param name:
    :return:
    """
    count = 1
    while count <= num:
        print('%s准备生产第%s个包子...' % (name, str(count)))
        time.sleep(3)
        q.put(count)  # 放入线程队列
        print('制作成功')
        count += 1


def consumer(name, num):
    """
    消费者
    :param name:
    :return:
    """
    count = 1
    while count <= num:
        time.sleep(5)
        if not q.empty():
            print('%s准备吃第%s个包子...' % (name, str(count)))
            q.get()
            print('享受中')
            count += 1
        else:
            print('不好意思,暂时没有包子,请稍等。')
            threading.Thread(target=producer).start()


if __name__ == '__main__':
    p = threading.Thread(target=producer, args=('A厨师', 4))
    c1 = threading.Thread(target=consumer, args=('顾客1', 2))
    c2 = threading.Thread(target=consumer, args=('顾客2', 3))
    p.start()
    c1.start()
    c2.start()

你可能感兴趣的:(动态规划,servlet,java)