一、进程的数据共享
进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过Manager实现数据共享。
把所有实现了数据共享的比较便捷的类都重新又封装了一遍,并且在原有的multiprocessing基础上增加了新的机制 list dict等
数据共享的机制
支持数据类型非常有限
list dict都不是数据安全的,你需要自己加锁来保证数据安全
Manager用法:
Manager().dict() # 创建共享的字典
Manager().list() # 创建共享的列表
用代码看看:
简单解释一下with:
with可以自动关闭文件、线程锁的自动获取和释放等事后清理工作。
紧跟with后面的语句会执行对象的 __enter__() 方法,这个方法的返回值将被赋值给as后面的变量。
当with后面的代码块全部被执行完之后,将调用对象的 __exit__()方法。
from multiprocessing import Manager,Process,Lock
def work(dic,lock):
with lock: # 多个进程对数据进行修改,不加锁的话会导致数据不安全
dic['count'] -= 1 # 每个进程都对dic进行修改
if __name__ == '__main__':
lock = Lock()
with Manager() as m:
dic = m.dict({'count':100}) # 创建共享的字典
p_lst = []
for i in range(100):
p = Process(target=work,args=(dic,lock))
p_lst.append(p)
p.start()
for p in p_lst:
p.join()
print(dic)
二、进程池的回调函数(同步提交apply没有回调函数)
场景:
子进程有大量的计算要去做,回调函数对结果做简单处理。
我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。
通过例子了解一下:
import os
from multiprocessing import Pool
def func(i):
print('子进程:',os.getpid())
return i
def call_back(res):
print('回调函数:',os.getpid())
print('res--->',res)
if __name__ == '__main__':
p = Pool()
print('主进程:',os.getpid())
p.apply_async(func,args=(1,),callback=call_back) # callback关键字传参,参数是回调函数
p.close()
p.join()
结果:
主进程: 4732
子进程: 10552
回调函数: 4732
res---> 1
从结果可以看出:
子进程func执行完毕之后才去执行callback回调函数
子进程func的返回值会作为回调函数的参数
回调函数是在主进程中执行的
应用实例:
url_lst = [
'http://www.baidu.com',
'http://www.4399.com',
'http://www.163.com',
'http://www.hao123.com',
'http://www.sina.com'
]
import re
from urllib.request import urlopen
from multiprocessing import Pool
def get_url(url):
res = urlopen(url) # 打开链接
web_name = re.search('www.(.*).com',url) # 网站名
print('%s finish' %web_name.group(1))
return web_name.group(1),res.read() # 返回网站名和连接的内容
def call_back(content): # 把链接的内容写入文件
web_name,con = content
with open(web_name+'.html','wb') as f:
f.write(con)
if __name__ == '__main__':
p = Pool()
for url in url_lst:
p.apply_async(get_url,args=(url,),callback=call_back)
p.close()
p.join()
三、线程的理论知识
1、进程概念
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的
2、进程的缺点
进程只能在一个时间做一件事,不能同时做两件事或多件事
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行
3、线程的概念
60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,
因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.
每一个进程中至少有一个线程。
4、进程和线程的关系
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信需要IPC(队列,管道等),同一个进程中的所有线程的资源是共享的
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程操作系统中,进程不是一个可执行的实体
5、线程使用场景
当某个进程肯定需要做不止一件事情的时候,比如你用QQ,你可以同时跟很多人聊天,而QQ只是一个进程,并不是说你跟一个人聊天就开一个进程这样,
因为聊天这些任务操作的都是同一块数据,因而不能用多进程。应该是你开了QQ这个进程,跟别人聊天的时候在这个进程里开启多个线程跟别人聊天。
6、线程的理解
多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。
同一台计算机上多个进程,则共享这台计算机的物理内存、磁盘、打印机等其他物理资源。多线程的运行跟多进程的运行类似,是cpu在多个线程之间的快速切换。
不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,比如QQ和迅雷抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的。
类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。
7、用户级线程和内核级线程
用户级线程
内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu。
在用户空间模拟操作系统对进程的调度,来调用一个进程中的线程,每个进程中都会有一个运行时系统,用来调度线程。此时当该进程获取cpu时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行。
内核级线程
切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用多核cpu,windows线程就是这样的。
混合实现
用户级与内核级的多路复用,内核同一调度内核线程,每个内核线程对应n个用户线程
8、python中的线程
全局解释器锁GIL
一个进程中的多个线程能够并行么?
在java c++ c# 等语言中是可以的
但是在python中是不可以的
python是一个解释型语言,所有的解释型语言都不行
为什么不行?
Cpython解释器内部有一把全局解释器锁 GIL
同一时刻用一个进程中的线程只有一个能被CPU执行
所以线程不能充分的利用多核
GIL锁是为了保证数据的安全性,虽然确实是限制了你的程序效率
但实际上GIL锁是目前能够帮助你在线程的切换中提高效率的手段
9、总结
进程是
计算机中最小的资源分配单位
进程对于操作系统来说还是有一定负担
创建一个进程 操作系统要分配的资源大致有 :
代码
数据
文件
为什么要有线程
轻量级的概念
他没有属于自己的进程资源:
一条线程只负责执行代码,没有自己独立的代码、变量、文件资源
什么是线程
线程是计算机中被CPU调度的最小单位
你的计算机当中的cpu都是执行的线程中的代码
线程和进程之间的关系
每一个进程中都有至少一条线程在工作
线程的特点
同一个进程中的线程共享这个线程的所有资源
轻量级 没有自己的资源
进程和线程之间的区别
占用的资源
调度的效率
资源是否共享
通用的问题
一个进程中的多个线程能够并行么?
在java c++ c# 等语言中是可以的
python中的线程
在python中一个进程中的多个线程能够并行么? 不行
python是一个解释型语言,所有的解释型语言都不行
为什么不行?
Cpython解释器 内部有一把全局解释器锁 GIL
所以线程不能充分的利用多核
同一时刻用一个进程中的线程只有一个能被CPU执行
GIL锁是为了保证数据的安全性,虽然确实是限制了你的程序效率
但实际上GIL锁是目前能够帮助你在线程的切换中提高效率的手段
GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。
线程有:
IO密集型线程和计算密集型线程
cpython解释器适合IO密集型线程(web 爬虫 金融分析)
如果要写计算密集型的线程:
要么换解释器,要么用多进程
四、threading模块
multiprocess模块完全模仿了threading模块的接口,二者在使用层面,有很大的相似性
1、线程的创建方式
方式一:
from threading import Thread
import time
def sleep_boy(name):
time.sleep(1)
print('%s is sleeping' %name)
t = Thread(target=sleep_boy,args=('xiaoming',)) # 这里可以不需要main,因为现在只是在一个进程内操作,不需要导入进程就不会import主进程了
t.start()
print('主线程')
方式二:
from threading import Thread
import time
class Sleep_boy(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
time.sleep(1)
print('%s is sleeping' % self.name)
t = Sleep_boy('xiaoming')
t.start()
print('主线程')
2、并发性(注意:在主进程下开启多个线程,每个线程的pid都跟主进程的pid一样)
import os
import time
from threading import Thread
def func(i):
time.sleep(0.5)
print('子线程:',i,os.getpid())
print('主进程:',os.getpid())
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
3、线程共享进程的资源
from threading import Thread
num = 100 # 全局变量
def func():
global num
num -= 1
t_lst = []
for i in range(100):
t = Thread(target=func)
t.start()
t_lst.append(t)
for t in t_lst:
t.join()
print('num:',num)
4、其他方法
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
is_alive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
from threading import Thread
import time
def func():
time.sleep(0.2)
print('hello')
t = Thread(target=func)
t.start()
print(t.isAlive()) # True
print(t.is_alive()) # True
print(t.getName()) # Thread-1
t.setName('t1')
print(t.getName()) # t1
threading模块提供的一些方法:
threading.currentThread(): 返回当前线程的对象(通过这个对象可以查看线程的一些属性,比如线程id:ident,线程的名字:getName等)
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import currentThread,enumerate,activeCount,Thread
import time
def func():
print('子线程:',currentThread().ident) # 子线程: 6076
print('子线程:',currentThread().getName()) # 子线程: Thread-1
time.sleep(3)
print('主线程:',currentThread().ident) # 主线程: 1156
print('主线程:',currentThread().getName()) # 主线程: MainThread
t = Thread(target=func)
t.start()
print(enumerate()) # [<_MainThread(MainThread, started 1156)>, <Thread(Thread-1, started 6076)>]
print(len(enumerate())) # 2
print(activeCount()) # 2
5、守护线程
守护进程和守护线程的区别:
1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束
2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,所以主线程结束了之后,守护线程随着主进程的结束自然结束了
import time
from threading import Thread
def func1():
while True:
time.sleep(0.5)
print('func1')
def func2():
print('func2 start')
time.sleep(3)
print('func2 end')
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.setDaemon(True) # 设置守护线程
# t1.daemon = True t1 = Thread(target=func1,daemon = True) 这两种方式也是开启守护线程的方法
t1.start()
t2.start()
print('主线程代码结束了')
守护进程:只能用 p.daemon = True 这种方式设置守护进程,因为daemon只是Process类对象的属性,
而守护线程,可以使用 t1 = Thread(target=func1,daemon = True)和t1.daemon = True这两种方式和t1.setDaemon(True)这种方法设置守护线程
因为daemon是Thread类对象的属性和对象的默认参数
守护进程(一种方式)
p = Process(target=func)
p.daemon = True # daemon是Process的属性
守护线程(三种方式)
t1 = Thread(target=func1,daemon = True)
t1.daemon = True
t1.setDaemon(True)
进程:
线程: