进程是资源分配最小单位
一个运行起来的程序就是一个进程
什么是程序(程序是我们存储在硬盘里的代码、文件)
当我们双击图标,打开程序的时候,实际上就是通过I/O操作(读写)内存条里面
内存条就是我们所指的资源
进程之间内存独立,不能相互访问
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
进程之间有自己独立的内存,各进程之间不能相互访问
创建一个新线程很简单,创建新进程需要对父进程进行复制
多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
单道编程: 计算机内存中只允许一个的程序运行
python提供了多种进程通信的方式,主要Queue和Pipe这两种方式,Queue用 于多个进程间实现通信,Pipe是两个进程的通信。
Queue有两个方法:
- Put方法:以插入数据到队列中
- Get方法:从队列读取并且删除一个元素
Pipe常用于两个进程,两个进程分别位于管道的两端
Pipe方法返回(conn1,conn2)代表一个管道的两个端,Pipe方法有duplex参数,默认为True,即全双工模式,若为FALSE,conn1只负责接收信息,conn2负责发送
managers
RabbitMQ、redis等
注意:不同进程之间内存是不共享的,所以互相之间不能访问对方数据。
利用Queues实现父进程到子进程(或子进程间)的数据传递
使用管道pipe实现两个进程间数据传递
Managers实现很多进程间数据共享
借助redis中间件进行数据共享
为什么我们需要进程池?
from multiprocessing import Process,Pool
import time,os
def foo(i):
time.sleep(2)
print("in the process",os.getpid()) #打印子进程的pid
return i+100
def call(arg):
print('-->exec done:',arg,os.getpid())
if __name__ == '__main__':
pool = Pool(3) #进程池最多允许5个进程放入进程池
print("主进程pid:",os.getpid()) #打印父进程的pid
for i in range(10):
#用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
pool.apply_async(func=foo, args=(i,),callback=call)
#用法2 串行 启动进程不在用Process而是直接用pool.apply()
# pool.apply(func=foo, args=(i,))
print('end')
pool.close() #关闭pool
pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率
进程的两个重要缺点
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
c. 例如,我们在使用QQ聊天,QQ做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即操作系统处理一会你的QQ任务,又切换到word文档任务上了,每个CPU时间片分给你的QQ程序时,你的QQ还是只能同时干一件事呀
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
无论你启多少个线程,你有多少个CPU, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
进程本身是无法自己执行的,要操作CPU,必须创建一个线程,线程是一系列指令的集合
所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
两个进程想通信,必须要通过一个中间代理
对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存
for循环同时启动多个线程:
import threading
import time
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start()
t.join(): 实现所有线程都执行结束后再执行主线
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
t_objs = [] #将进程实例对象存储在这个列表中
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start() #启动一个线程,程序不会阻塞
t_objs.append(t)
print(threading.active_count()) #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
t.join() #阻塞某个程序
print(threading.current_thread()) #打印执行这个命令进程
print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)
setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出:
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置
t.start() #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)
GIL全局解释器锁:保证同一时间仅有一个线程对资源有操作权限:
作用:在一个进程内,同一时刻只能有一个线程执行
说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
线程锁:
有了GIL全局解释器锁为什么还需要线程锁?
因为cpu是分时使用的
GIL是限制同一个进程中只有一个线程进入Python解释器。。。。。
而线程锁是由于在线程进行数据操作时保证数据操作的安全性(同一个进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)
其实线程锁完全可以替代GIL,但是Python的后续功能模块都是加在GIL基础上的,所以无法更改或去掉GIL,这就是Python语言最大的bug…只能用多进程或协程改善,或者直接用其他语言写这部分
两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去
用户锁:
import time
import threading
lock = threading.Lock() #1 生成全局锁
def addNum():
global num #2 在每个线程中都获取这个全局变量
print('--get num:',num )
time.sleep(1)
lock.acquire() #3 修改数据前加锁
num -= 1 #4 对此公共变量进行-1操作
lock.release() #5 修改后释放
Semaphore(信号量):
多线程
GIL锁:
全局解释锁,每次只能一个线程获得cpu的使用权:为了线程安全,也就是为了解决多线程之间的数据完整性和状态同步而加的锁,因为我们知道线程之间的数据是共享的。
join()作用:
在进程中可以阻塞主进程的执行, 直到等待子线程全部完成之后, 才继续运行主线程后面的代码
setDaemon():
将该线程标记为守护线程或用户线程
线程池
import requests
from concurrent.futures import ThreadPoolExecutor
def fetch_request(url):
result = requests.get(url)
print(result.text)
url_list = [
'https://www.baidu.com',
'https://www.google.com/', #google页面会卡住,知道页面超时后这个进程才结束
'http://dig.chouti.com/', #chouti页面内容会直接返回,不会等待Google页面的返回
]
pool = ThreadPoolExecutor(10) # 创建一个线程池,最多开10个线程
for url in url_list:
pool.submit(fetch_request,url) # 去线程池中获取一个线程,线程去执行fetch_request方法
pool.shutdown(True) # 主线程自己关闭,让子线程自己拿任务执行
什么是协程(进入上一次调用的状态)
协程,又称微线程,纤程,协程是一种用户态的轻量级线程。
线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)
协程能在单线程处理高并发
协程的定义:
协程的优点:
协程的缺点:
协程处理并发:
Gevent
遇IO自动切换
Greenlet
遇IO手动切换
Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。