进程:正在运行的程序实例,是对资源管理的集合
线程:系统调度的最小单位,是对执行指令的集合
协程:用户态的线程,主动的协助式工作
对比:
1、进程不能单独运行,每个进程创建时都会主动创建一个子线程用来执行具体操作
2、单个进程中可以存在多个子线程,第一个创建的子线程为主线程,线程可以再创线程
3、单个进程中,所有线程都是平等的,共享同一块内存空间(进程内存空间)
4、启动线程比启动进程快,每个进程内存独立
5、创建子进程相当于克隆一份父进程的内存空间
6、进程间不能直接访问,除父进程访问子进程之外,其他进程间相互访问必须通过中间代理,但是同一进程中的所有线程都可以相互访问和操作。
一、协程
1、简介
协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。他本身是一种特殊的子程序或者称作函数
通过yeild实现的生产消费者模型就是典型的协程。
2、优点:
无须线程上下文切换的开销
无须数据操作锁定与同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题,适用于处理高并发
3、缺点:
无法利用多核资源,协程的本质是个单线程,无法将CPU的多核用上
进行阻塞操作时,会阻塞掉整个程序
4、Greenlet(封装好的协程模块)
为了保证并发效果,遇到 IO操作就切换,操作完后切换回来,Greenlet实现了这一功能
greenlet.swith() #主动式的切换
5、Gevent(自动切换)
Gevent封装了Greenlet并且将手动切换修改为了自动切换
可以轻松的通过Gevent实现并发同步或异步编程
二、多进程(multiprocessing)
p = multiprocessing.Process(target=func, args(i,)) #创建进程
# func函数中必须包含创建线程的操作
1、获取进程信息
name:调用进程的模块名称
os.getppid:父进程ID
os.getpid:子进程ID
2、进程间的数据交互
进程间的内存是独立的,必须通过中间件进行交互。
提供了三种方法:
队列(Queue)
import Queue
def func(q):
q.put(value) #将value放入队列中
q = Queue()
p = multiprocessing.Process(target=func, args(q))
p.start()
print q.get() #取出value
p.join() #等待进程执行结束
看上去像是数据共享,但实际上是克隆了一份Queue交给子进程,子进程将Queue序列化后交给中间件进行反序列化再交给其他进程。
管道(Pipes):
类似socket,建立一个沟通桥梁,两个进程分别在两端进行数据交互操作
数据共享(Manager)
import multiprocessing from Manager, Process
with Manager() as manager:
d = manager.dict() #生成一个可供多个进程间共享的字典
3、进程池(Pool)
由于创建子进程时就是克隆一份父进程的内存空间给子进程,操作时内存损耗过大,这时候就需要进程池来对进程数量进行限制。
进程池内部维护着一个序列,存储多个进程信息,调用进程时向进程池发起请求,如果进程池内没有可供使用的进程,则等待至有进程响应请求为止。
#方法有 appley(同步、串行) 、apply_async(异步、并行)
import multiprocessing import Pool
pool = Pool(process=num) #num:进程池最大进程数量
for i in range(10):
pool.apply_async(func=func, args=(i), callback=cbk)
#func 进程操作指令,i参数, cbk 回调函数
p.close() #程序结束前必须先关闭进程池,再join等待进程结束
p.join() #进程池中进程执行完毕后退出
四、多线程(threading)
1、调用方法
threading.Thread(target=func, args(i,)) #直接创建线程
class MyThread(threading.Thread) #类继承创建线程
2、使用for循环可以创建多个线程,每个创建的线程不会等待前一个线程执行完毕,线程是异步、并行的。
3、等待子线程执行结果的方法
队列(Queue)
作用:增加双方的效率,完成程序的解耦,松耦合。
队列是有序的,可以理解为一个存放线程的容器。
#三种形态
queue.Queue(maxsize=n) #FIFO先入先出模式
queue.LifoQueue(maxsize=n) #LIFO先入后出模式
queue.ProiorityQueue(maxsize=n) #存储时可以指定优先级
#基本方法
q = queue.Queue(10) #实例化队列对象
q.put() #存放
q.get() #取出
q.size() #查看大小
q.get_nowait() #没有数据取出时弹出异常
q.put(1, value) #指定优先级,1为优先等级,越小越高
join() 等待线程结束
t_objs = [] #创建用来存放线程的列表
for i in range(10):
t = threading.Thread(target=func, args(i))
t.start()
t_objs.append(t)
print "现在活动的线程总数为:",threading.active_count()
for t in t_objs:
t.join() #t.wait等待线程执行结束后再结束进程
GIL全剧解释器锁
python可以创建多个线程并平均分布到计算机的多核中,但是同时只能有一核在处理数据。
GIL全局解释器锁保证了数据的统一,在多线程修改数据时,为了防止数据混乱,对多线程进行限制变为串行处理。
线程锁(threading.Lock互斥锁)
线程之间相互沟通,并保证同一时间只有一个线程在对数据进行修改
lock = threading.Lock()
lock.acquire() #上锁,数据修改操作前
lock.release() #解锁,数据修改操作后
递归锁(threading.RLock)
当设计多线程中存在多个互斥锁时,解锁就会混乱,这时候需要递归锁,从最底层锁一层一层向上解锁。
信号量(threading.BoundedSemaphore())
互斥锁只允许同一时间一个线程进行数据修改操作,而Semaphore允许多个。
semaphore = threading.BoundedSemaphore(num) #最多num个
# 上锁与解锁与Lock,RLock一致
事件events(threading.Events())
控制线程之间的交互,就是设置一个全局变量,负责线程之间的通信。
events = threading.Events() #声明
events.set() #设置标志位
events.clean() #清除标志位
events.is_set() #检测标志位
events.wait() #等待标志位设定后继续执行
生产消费者模型
在并发编程中,生产消费者模型可以解决大部分的并发问题。
该模式通过平衡生产线和消费线程的工作能力来提高整体的处理速度。
生产消费者模型是通过一个容器来解决生产消费的强耦合问题。
生产者和消费者之间无序彼此通信,而通过阻塞队列来进行通讯,生产者生产完成后无须等待消费者消费,直接丢给阻塞队列,消费者请求消费时也无须提醒生产者,直接从阻塞队列获取。阻塞队列形成了一个缓冲区,平衡了生产者和消费者的处理能力。
简单模型代码:
import queue
import threading
import time
q = queue.Queue(maxsize=10)
def produce(name):
count = 1
while True:
q.put('馒头')
print "%s生产馒头*1"%name
count += 1
time.sleep(1)
def consumer(name):
while True:
print "%s吃掉一个馒头"%name
time.sleep(2)
p = threading.Thread(target=produce, args=('p01'))
c1 = threading.Thread(target=consumer, args=('c01'))
c2 = threading.Thread(target=consumer, args=('c02'))