Python系统学习-12

1. 进程

1.1队列

1.2生产者消费者模型

这里写图片描述
多进程是用来解决计算型的程序用
这里写图片描述

2.线程

2.1理论

这里写图片描述

  • 线程是执行程序的
  • 线程是计算机中CPU调度的最小单位
    为什么要有线程
  • 为了节省操作系统的资源
  • 在实现并发的时候,能够减少开启,销毁进程时间开销
  • 效率差:线程比进程的开启和销毁消耗的时间长

2.2python中使用多线程

2.3特点

进程和线程区别:
这里写图片描述

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

缺点:
这里写图片描述
解决缺点:多进程+多线程

线程不能被强制终止

2.4守护线程

2.5锁

2.5.1互斥锁

  • 互斥锁最节省资源的

2.5.2递归锁

  • 为了临时解决上线问题
    这里写图片描述

2.5.3死锁

3.池

如果任务多的情况下,无限的开启进程、线程不仅会浪费非常多的时间来开启和销毁,还需要占用系统的调度资源。
如果我开启有限的线程、进程,来完成无限的任务,这样能够最大化的保证并发,且维护操作系统资源的协调。

  • 任务数超过了CPU个数的2倍
  • 进程的个数就不应该和任务数相等了
  • 任务数超过了CPU个数的5倍
  • 线程的个数就不应该和任务数相等了
    现在用线程和进程池:
from concurrent.futures import ThreadPoolExecuter
from concurrent.futures import ProcessPoolExecuter

这里写图片描述

3.1进程池

3.2线程池

4.协程

  • 一条线程分成几个任务执行,每个任务执行一会,在切到下一个任务。
  • 切换任务是由程序来完成的,不是由操作系统控制的。
  • 如果在执行任务的过程中是遇到阻塞才切换,是能够节约时间的
  • 协程本质是一条线程
  • 多个协程也是宏观上的并发
  • 协程是数据安全的,不需要考虑锁

优点如下:

1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  • 必须在只有一个单线程里实现并发
  • 修改共享数据不需加锁
  • 用户程序里自己保存多个控制流的上下文栈
  • 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
from gevent import monkey;monkey.patch_all() #from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

import gevent
import time

def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

def play():
    print('play 1')
    time.sleep(1)
    print('play 2')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1,g2])
print('main')

5.IO模型

  • 刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

  • 等待数据准备 (Waiting for the data to be ready)
    将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
    正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。

  • 阻塞 I/O(blocking IO)
    非阻塞 I/O(nonblocking IO)
    I/O 多路复用( IO multiplexing)
    信号驱动 I/O( signal driven IO)
    异步 I/O(asynchronous IO)
    注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

典型的读操作流程大概是这样:
这里写图片描述
>

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

非阻塞 I/O(nonblocking IO)
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
这里写图片描述

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

5.1多路复用IO

6.线程队列

import queue

q = queue.Queue()
# 在线程之间数据安全,自带线程锁的数据容器
lq = queue.LifoQueue()
# 栈,先进后出
lq.put(1)
print(lq.get())

qp = queue.PriorityQueue() #优先级队列
qp.put(2)
qp.put(3)
qp.get() # 输出2

猜你喜欢

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