python_并发编程

本文深入探讨了操作系统的历史,从无操作系统到个人计算机的发展,强调了操作系统在资源管理和程序员效率中的作用。文章重点介绍了进程作为资源单位,详细讲解了并发和并行的概念,以及Python中的`multiprocessing`和`threading`模块,包括进程的创建、通信、同步和线程的使用。此外,还讨论了Python的GIL全局解释器锁及其对多线程的影响,以及如何利用互斥锁、条件变量等解决并发问题。
摘要由CSDN通过智能技术生成

目录

一、背景知识

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

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

2、操作系统历史

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

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

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

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

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

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

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

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

一种早期的批处系统:

        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 总结

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

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

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(
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值