Python系统学习-11

1.UDP协议

这里写图片描述

server:
import socket

udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.bind(('127.0.0.1',8898))
msg, addr = udp_sk.recvfrom(1025)
print(msg)
udp_sk.sendto(b'hi', addr)
udp_sk.close()
=====================================
client:
import socket

ip_port = ('127.0.0.1', 8898)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello', ip_port)
back_msg, addr = udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'), addr)

2.高并发

2.1操作系统背景

必备的理论基础:

一、操作系统的作用:

1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

二、多道技术:

1.产生背景:针对单核,实现并发
ps:
现在的主机一般是多核,那么每个核都会利用多道技术
有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
cpu中的任意一个,具体由操作系统调度算法决定。
2.空间上的复用:如内存中同时有多道程序
3.时间上的复用:复用一个cpu的时间片
   强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
        才能保证下次切换回来时,能基于上次切走的位置继续运行

2.1.1什么是进程?

  • 进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
  • 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3]
    进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
  • 进程由程序、数据和进程控制块三部分组成。
  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
  • 程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
  • 程序是永久的,进程是暂时的。
  • 注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

2.1.2进程调度

要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。

先来先服务调度算法

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
短作业优先调度算法
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
时间片轮转法
多级反馈队列

  • CPU在两个程序之间切换?
    遇见不需要CPU工作的代码的时候:input,f.read, time.sleep(), f.write(IO操作:输入输出),print,socket.accept,socket.recv等
  • 提高CPU工作效率:短作业优先算法、分时系统(虽然降低了效率,但是提高了用户使用体验):所有作业在操作系统的调度下轮流被CPU执行

2.1.2进程

  • 运行当中的程序,进程是计算机中资源分配的最小单位。
  • PID=process id
  • ppid=parent process id
    这里写图片描述
    这里写图片描述

2.1.3进程的创建与结束

2.1.3.1进程的创建

但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
  而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:
  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
  无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
  

创建进程

  1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
      2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
      关于创建子进程,UNIX和windows
      1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
      2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

2.1.3.2进程的结束

  1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
  2. 出错退出(自愿,python a.py中a.py不存在)
  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)
  4. 被其他进程杀死(非自愿,如kill -9)

2.1.4在python程序中的进程操作

之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。以我们之前所学的知识,并不能实现创建进程这个功能,所以我们就需要借助python中强大的模块。

2.1.5multiprocess模块

仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。

process模块介绍

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,’egon’,)
4 kwargs表示调用对象的字典,kwargs={‘name’:’egon’,’age’:18}
5 name为子进程的名称

2.1.6生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

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

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

什么是生产者消费者模式

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

2.1.7管道

创建管道的类:

Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道

参数介绍:
dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。

主要方法:
conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
#其他方法:
conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
conn1.fileno():返回连接使用的整数文件描述符
conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。

  • conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
    conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收

  • conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。

2.1.8进程之间的数据共享

  • 展望未来,基于消息传递的并发编程是大势所趋
  • 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据。
  • 这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中。
  • 但进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题。
  • 以后我们会尝试使用数据库来解决现在进程之间的数据共享问题。

  • 进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
    虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此

  • A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
  • A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.

2.1.9进程池和multiprocess.Pool模块

进程池

-为什么要有进程池?进程池的概念。
- 在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?
- 在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。

multiprocess.Pool模块

2.2同步

做完一件事情,在做另外一件事情。

2.3异步

做一件事情事情,可以在做另一件事情

2.4阻塞

recv、sleep、accept、input、recvfrom

2.5非阻塞

  • 没有遇见上面这些阻塞的情况就是非阻塞

2.6并行与并发

  • 并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )

  • 并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

  • 区别:并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
    并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。

2.7进程

  • 进程是计算机中资源分配的最小单位。
  • PID process id 进程的id
  • ppid parent porcess id 父进程

2.7.1主进程

import os
import time
from multiprocessing import Process

n = 100
def func():
    global n
    time.sleep(1)
    n -= 1
    print('子进程',os.getpid(),os.getppid())

if __name__ == '__main__':
    print(os.getpid(),os.getppid())
    p_lst = []
    for i in range(50):
        p = Process(target=func)
        p.start()
        p_lst.append(p)
    for p in p_lst:
        p.join()
    print('主进程')
    print('--->',n)

2.7.2子进程传参

import os
import time
from multiprocessing import Process
def func(*args):
    print('in func before', os.getpid(),*args)
    time.sleep(1)
    print('in func after',os.getpid())

if __name__ == '__main__':
    Process(target=func,args=(1,2,3,)).start()
  • if name == ‘main‘: 在win上必须写,在其他操作系统上可不写
  • 主进程和子进程的异步 并发效果
  • 主进程会等待子进程结束之后在结束, 为什么主进程要等待子进程结束(要回收子进程占用的资源)
  • 在主进程中终止一个子进程
  • 开启多个子进程,多个子进程之间也是异步的
  • 多个子进程的join,join会等待子进程的结束而结束
  • 数据隔离:进程之间的资源 数据都是隔离的
  • 子进程传参数
  • 面向对象的方式开启子进程
  • terminate isalive

2.7.3面向对象的方式开启子进程

import os
import time
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self,arg):
        super().__init__()
        self.arg = arg

    def run(self):
        print(self.arg)
        time.sleep(1)
        print('in run',os.getpid())

if __name__ == '__main__':
    print('main : ',os.getpid())
    p = MyProcess(123)
    p.start()
    print(p.is_alive())
    p.terminate()   # 非阻塞的
    print(p.is_alive())
    time.sleep(0.01)
    print(p.is_alive())

2.8multiprocessing

在windos系统上必须写if __name__== ‘__main__’:
这里写图片描述

主进程要回收子进程资源

2.9守护进程

主进程的代码执行完毕之后,子进程(守护进程)就自动结束

import os
import time
from multiprocessing import Process
def func2():
    print('func2 before', os.getpid())
    time.sleep(5)
    print('func2 after',os.getpid())

def func():
    print('in func before', os.getpid())
    time.sleep(3)
    print('in func after',os.getpid())

if __name__ == '__main__':
    p = Process(target=func)
    p.daemon = True   # 将p设置为一个守护进程
    p.start()
    p = Process(target=func2)
    p.start()
    time.sleep(1)
    print('主进程',os.getpid())

2.10同步控制-锁

这里写图片描述

import json
import time
from multiprocessing import Process,Lock

def search(i):
    with open('ticket_db.py') as f:
        ticket = json.load(f)
    time.sleep(0.1)
    print('%s查询余票 : '%i,ticket['count'])

def buy_ticket(i,lock):
    lock.acquire()
    with open('ticket_db.py') as f:
        ticket = json.load(f)
    time.sleep(0.1)
    if ticket['count'] > 0:
        ticket['count'] -= 1
        print('%s购票成功'%i)
    time.sleep(0.1)
    with open('ticket_db.py','w') as f:
        json.dump(ticket,f)
    lock.release()

def get(i,lock):
    search(i)
    buy_ticket(i,lock)

if __name__ == '__main__':
    lock = Lock()
    for i in range(20):
        p = Process(target=get,args=(i,lock))
        p.start()
==============================================
ticket_db.py
{"count": 1}

互斥锁:lock.acquire()两次就是互斥锁。

3.队列

帮助你保证进程之间的数据安全

from multiprocessing import Queue,Process
# IPC  Inter-process communication
def func(q):
    print(q.get())

if __name__ == '__main__':
    q = Queue()
    p = Process(target=func,args=(q,))
    p.start()
    q.put(12345)

3.1IPC

队列底层就是管道+锁组成。

4.线程

有了进程为什么要有线程

  • 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

  • 如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。

  • 现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。

线程的出现

60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
  因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
     每一个进程中至少有一个线程。

进程和线程的关系
这里写图片描述

线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中,进程不是一个可执行的实体。
  *通过漫画了解线程进城

猜你喜欢

转载自blog.csdn.net/weixin_41765871/article/details/81263834
今日推荐