疫情环境下的网络学习笔记 python 4.24

4.24作业

整理并发编程三天内容理论,用自己的概述

  1. 多道技术

    允许多个程序进入内存,由CPU交替执行。当一个程序进入IO操作,这个程序进入阻塞,所以此时CPU马上切换到另一个程序运行,这样在等待IO操作的时候CPU也能得到利用,榨干CPU

  2. 进程理论

    所有程序进入运行态前必须经过就绪态排队

    img

    • 运行态就是程序运行中的计算,创建名称空间,print,打开文件一类的
    • 阻塞态就是CPU不做运算的状态,包括time.sleep,input一类
    • 就绪态是阻塞态结束,包括time.sleep结束,input输入之后获取到值一类。结束后进入就绪态排队,等待CPU来运行
  3. 同步和异步

描述任务的提交方式

  • 同步:原地等待结果,得到结果后继续往下运行

  • 异步:不等待结果,直接做其他事情,微信小程序里的onLoad,通过回调机制获取结果

    程序的最佳形式是异步+非阻塞

  1. 开启进程的两种方式

    • 进程要运行的代码写在函数内,使用 multiprocessing 模块,生成对象,指定target,args,调用对象的start方法,开启进程

      from multiprocessing import Process
      def task(name):
      	pass
      if __name__ == '__main__':
      	p = Process(target=task,args=('deimos',))
      	p.start()
      

      windows操作系统下,开启进程一定要在main里开启:因为Windows中创建子进程会自动import当前这个py文件,因此会发生递归,所以要用main保护起来,import的时候不调用

    • 继承Process类的方法

      导入multiprocessing,写一个类,类里面定义run函数,run里面放进程要做的事情,生成对象,调用start方法,会启动进程,执行run

      from multiprocessing import Process
      import time
      class MyProcess(Process):
      	def run(self):
      		print('sleep')
      		time.sleep(1)
      		print('get up')
      		
      if __name__ == '__main__':
      	p = Myprocess()
      	p.start()
      	print('main')
      

    创建进程就是在内存中申请一块内存空间,将需要运行的代码丢进去,一个进程对应在内存中就是一块独立的内存空间

  2. join方法

    主进程等待子进程结束之后,再继续往后执行,结果就是把异步变成同步

  3. 进程对象其他方法

    一台计算机上运行着很多进程,计算机会给每一个运行的进程分配一个 PID号 ,是每一个进程的唯一标识,可以通过multiprocessing的current_process和os模块查看

    from multiprocessing import Process,current_process
    import os
    print('%s is running'%current_process().pid)
    # 获取当前pid
    print(os.getpid())
    # 获取父进程的pid
    print(os.getppid())
    # 查看一个是否存活
    p.is_alive()
    # 杀死一个进程
    p.terminate()
    
  4. 僵尸进程与孤儿进程

    • 僵尸进程:父进程开设了子进程,在父进程结束之后不会立马释放PID号,而是会保留一段时间,可以通过父进程查看子进程
    • 孤儿进程:子进程在运行,父进程结束了,则操作系统会接管,回收子进程的资源
  5. 守护进程

    在start开启进程上方,指定进程p为守护进程 p.daemon = True

    • 其一:守护进程会在主进程代码执行结束后立即终止
    • 其二:守护进程内无法再开启子进程,否则抛出异常

    指定守护进程必须在start上方

  6. 互斥锁

    多个进程操作同一份数据的时候,会出现数据错乱的问题,针对上述问题,加锁处理:将并发变成串行,牺牲效率,保证了数据安全

    原理:在主进程中生成一把锁,让所有的子进程抢,谁先抢到,谁先操作数据。一个进程抢到了,进程结束释放之后,别的进程接着抢

    from multiprocessing import Process,Lock
    # 从这个模块里导入Lock
    # 在每个进程抢文件的操作前生成一个锁
    mutex.acquire()
    # 抢到了锁,对文件操作
    buy(i)
    # 操作完了,释放锁给别的进程抢
    mutex.release()
    

    实际编程中很少使用锁,使用锁的时候有可能造成死锁现象

  7. 队列 Queue

    先进先出,创建队列对象,有一系列方法可以使用

    使用队列

    • 使用 multiprocessing,从中导入Queue:from multiprocessing import Queue,直接使用q = Queue()
    from multiprocessing import Queue
    # 创建一个队列,括号内可以传数字,表示生成队列最大可以存放的数据量max_size
    q = Queue(5)
    # 往队列中存数据:put
    q.put(100)
    q.put(200)
    q.put(300)
    # 当队列数据放满了,继续put,程序会阻塞,直到有位置让出来,多的数据入队
    # 在队列中取数据:get
    val = q.get() # 100
    # 队列中如果已经没有数据,get方法会原地阻塞
    
    val6 = q.get_nowait() # 没有数据直接报错
    val7 = q.get(timeout = 3) # 等待时间3秒,超过3秒没有取出数据,报错
    q.full() # 返回判断队列是否满了
    q.empty() # 返回判断当前判断是否为空
    
    # 当多进程操作一个队列的时候,结果会不准确,所以使用队列的时候配合锁一起操作数据
    
  8. 借助队列实现进程间通信

    上个例子

    from multiprocessing import Process,Queue
    
    # 1. 主进程跟子进程借助队列通信
    def producer(q):
        q.put('1')
        print('producer')
    
    # 2. 子进程跟子进程借助队列通信
    def consumer(q):
        print(q.get())
    
    if __name__ == '__main__':
        q = Queue()
        p = Process(target=producer, args=(q,))
        p1 = Process(target=consumer, args=(q,))
        p.start()
        p1.start()
    
  9. 生产者消费者模型

    就是借助队列,一个进程产生数据,一个进程接收数据,两个角色之间需要一个媒介来传输数据:队列

    get和put都会原地阻塞,所以会一直产生一直接收

    要消费者结束接收,可以让生产者在结束的时候生产指定的符号,让消费者接收

    # 生产者+消息队列+消费者
    # JoinableQueue:可以被等待的,带有计数器的queue
    # 在往队列中放数据的时候,计数器自动+1,从队列中取数据的时候,调用task_done方法,计数器自动-1
    q.join() # 当计数器为0的时候才继续往下运行
    
  10. 线程理论

    • 进程表示的是资源单位,真正干活的是线程,执行单位
    • 将操作系统比喻成一个工程,进程相当于车间,线程相当于车间里的流水线
    • 进程和线程都是虚拟单位,只为了更加方便地描述问题
  11. 开启线程的方式

    • 与开启进程相似,使用模块生成对象,指定target;继承类

    • 同一进程下的线程数据共享,线程也可以像进程一样使用join方法

    • 线程也可以指定守护线程,和守护进程方法一样

      t = Thread(target=task)
      t.daemon = True
      t.start()
      
    • 线程也可以使用互斥锁,与进程一样

      from multiprocessing import Lock
      mutex  = Lock()
      # 在每个线程中
      mutex.acquire()
      # 操作
      mutex.release()
      
    from threading import Thread
    import time
    def task(name)
    	print('%s running'%name)
    	time.sleep(1)
    	print('%s over'%s)
    	
    # 开启线程不需要在main下执行代码,直接书写就可以
    # 但是还是习惯性地将启动命令写在main下面
    t = Thread(target=task,args=('aaa',))
    t.start()
    # 创建线程的开销非常小,几乎代码一执行,就创建了
    
    # 第二种方式
    from threading import Thread
    import time
    class MyThread(Thread):
    	def __init__(self,name):
    		super().__init__()
    		self.name = name
    		
    	def run(self):
    		print('%s running'%name)
    		time.sleep(1)
    		print('%s over'%s)
    
    if __name__ == '__main__':
    	t = MyThread('aaa')
    	t.start()
    	print('主线程')
    
  12. 使用多线程实现TCP并发

    # 服务端
    server = socket.socket() # 不写参数,默认是TCP
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    	
    # 将服务的代码单独封装成一个函数
    def talk(conn):
    	while True:
    		try:
    			data = conn.recv(1024)
    			if len(data == 0):break
    			print(data.decode('utf-8'))
    		except Exception as e:
    			print(e)
    	conn.close()
    	
    # 连接循环,使用线程实现并发
    while True:
    	conn,addr = server.accept() # 自动阻塞
        # 一旦接收到链接,就开启一个线程
    	t = Thread(target=talk,args=(conn,))
    	t.start()
    
  13. 互斥锁可以使用with上下文

    with mutex:
    	tmp = money
    	time.sleep(0.1)
    	money = tmp - 1
    # 自动抢锁,结束了自动释放锁
    
  14. 全局解释器锁GIL

    解释器的特性,不是python的特性

    GIL保证同一时刻只有一个线程执行代码,每个线程在执行过程中都要先获取GIL

    因为GIL的存在,python中的多线程其实是伪多线程,即使在多核的情况下也只能发挥出单核的性能

掌握如何开设进程和如何开设线程的代码

开设进程

# 1. 导入multiprocessing,Process
# 2. 把进程要做的事情放在函数里
# 3. 生成Process对象,指定target为进程的函数,args为函数参数
# 4. 可选:p.daemon=True可以指定守护进程
# 5. start() 开启进程

开设线程

# 1. 导入threading,Thread
# 2. 把进程要做的事情放在函数里
# 3. 生成Thread对象,指定target为线程的函数,args为函数参数
# 4. 可选:p.daemon=True可以指定守护线程
# 5. start() 开启线程

利用多进程或多线程自己实现TCP服务端的并发

# 服务端
import socket
from threading import Thread
# 服务端
server = socket.socket()  # 不写参数,默认是TCP
server.bind(('127.0.0.1', 8080))
server.listen(5)


# 将服务的代码单独封装成一个函数
def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            data = data.decode('utf-8')
            print('接收到的信息:',data)
            conn.send('歪比巴布'.encode('utf-8'))
            if len(data) == 0 : break
        except Exception as e:
            print(e)
            conn.close()
            # 一个链接退出之后,当前线程结束,主进程中仍然在接收链接
            break


# 连接循环,使用线程实现并发
while True:
    conn, addr = server.accept()  # 自动阻塞
    print(conn,addr)
    # 一旦接收到链接,就开启一个线程
    t = Thread(target=talk, args=(conn,))
    t.start()
# 客户端
import socket,time

client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
    client.send('client 1'.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode('utf-8'))
    time.sleep(0.5)

整理python基础阶段知识点及项目代码,ATM购物车,选课系统一定要自己脱稿从头到位敲出来

预习并发编程剩余知识点

GIL锁与自定义锁,多线程工作顺序图解

https://www.cnblogs.com/the3times/

![993481088cc47a0e9b7df2fca5ce4c5](C:\Users\ADMINI~1\AppData\Local\Temp\WeChat Files\993481088cc47a0e9b7df2fca5ce4c5.png)

posted @ 2020-04-26 12:04  黑猫警长紧张  阅读(118)  评论(0)    收藏  举报