复习
为什么要使用进程池:为了实现并发,然后再并发的基础上控制进程的数目
异步调用p.apply_async().get()--相当于同步调用
回调函数,前一个函数结束后自动触发,且只有一个参数,就是前一个函数的返回值
(*书--现在操作系统(涉及到操作系统和硬件))
必备的知识--- 计算机硬件,操作系统,计算机网络,网络,数据结构
线程
进程只是用来把资源集中到一起(开辟一块空间,进程只是一个资源单位,或者说资源集合),
线程是CPU上的执行单位,开一个进程就是开启一个主线程(或者叫控制线程)
多线程共享一个进程里的资源
开启线程:
![]()
from threading import Thread
def work(n):
print("%s is running"%n)
if __name__ == "__main__":
t = Thread(target=work,args=(1,))
t.start()
print("主") # 站在执行的角度,就是主线程
# 站在资源的角度,就是主进程
方法1
![]()
from threading import Thread
class Mythread(Thread):
def __init__(self,n):
super().__init__()
self.n = n
def run(self):
print("%s is running"%self.n)
if __name__ == "__main__":
t = Mythread(2)
t.start()
print("主") # 站在执行的角度,就是主线程
# 站在资源的角度,就是主进程
方法2
线程和进程的区别:
统一进程的多个线程共享该进程的资源
创建新的线程开销要远远小于创建新的进程
多线程--在一个进程中开启多个线程,如果想要多个任务共享一块地址空间,就要在一个进程里开多个线程
使用多线程的优点:
1--多线程共享一个进程的内存空间
2--线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
3--若多个线程都是cpu密集型的,那么在cpython中并不能获得性能上的增强,
如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4--在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于cpython)
cpyhton里边,一个进程开多个线程,同一时间只会有一个线程执行
进程和线程的对比
子线程的pid和父线程的pid是一样的
同一进程内的多个线程共享该进程的资源
![]()
from threading import Thread
from multiprocessing import Process
import os
def work(n):
print("%s is running"%os.getpid())
if __name__ == "__main__":
t = Thread(target=work,args=(1,))
p = Process(target=work,args=(1,))
# p.start()
t.start()
print("主%s"%os.getpid())
pid
![]()
from threading import Thread
from multiprocessing import Process
n = 100
def work():
global n
n = 0
if __name__ == "__main__":
t = Thread(target=work)
p = Process(target=work)
# p.start()
t.start()
t.join()
print("主--%s"%n)
共享资源
线程对象的其他方法
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
![]()
from threading import Thread,current_thread,enumerate,activeCount
from multiprocessing import Process
def work():
print("%s is running"%current_thread().getName())
if __name__ == "__main__":
t = Thread(target=work)
p = Process(target=work)
# p.start()
t.start()
t.join()
# print(t.is_alive()) # -- 是否活着
print(t.getName()) # -- 获取线程名
print(t.setName("线程1")) # -- 设置线程名
# print(current_thread().getName()) # -- 当前线程名
print(enumerate()) # 当前活跃的线程,列表的形式
# print(activeCount()) # 当前活跃的线程数
print("主--%s"%current_thread().getName())
线程对象的其他方法
守护线程
主线程从执行角度就代表该进程,当主线程所在进程里的非守护线程全部结束后才结束
守护线程会在主线程结束后结束
![]()
from threading import Thread,current_thread
import time
def work():
print("%s is running"%current_thread().getName())
time.sleep(2)
print("%s is done" % current_thread().getName())
if __name__ == '__main__':
t = Thread(target=work)
t.daemon = True
t.start()
print("主")
守护线程
![]()
from threading import Thread,current_thread
import time
def foo():
print(123)
time.sleep(2)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print("主")
迷惑例子
***** GIL(全局解释器锁) -- cpython的特性
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
GIL本质就是一把互斥锁,本质是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
每次执行python程序,都会产生一个独立的进程。
在一个python进程内
1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
cpu到底是用来做计算的,还是用来做I/O的?
多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处
对计算来说,cpu越多越好,但是对于I / O来说,再多的cpu也没用
当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),
这是因为一个程序基本上不会是纯计算或者纯I / O,所以我们只能相对的去看一个程序到底是计算密集型还是I / O密集型,从而进一步分析python的多线程到底有无用武之地
分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程
单核情况下,分析结果:
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I / O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I / O密集型,再多的核也解决不了I / O问题,方案二胜
结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
![]()
from threading import Thread,Lock
import time
n = 100
def work():
global n
mutexA.acquire() # 加锁保证了一次只有一个线程进来改变n的值
time.sleep(0.1)
temp = n
n = temp - 1
mutexA.release()
if __name__ == '__main__':
t_l = []
mutexA = Lock()
for i in range(100):
t = Thread(target=work)
t_l.append(t)
t.start()
for i in t_l:
i.join()
print(n)
GIL
同步锁
1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高
![]()
from multiprocessing import Process
from threading import Thread
import time
def work():
time.sleep(2) # 模拟io操作
if __name__ == '__main__':
l = []
start = time.time()
for i in range(40):
p = Process(target=work) #2.1911327838897705
# p = Thread(target=work) #2.002037763595581
l.append(p)
p.start()
for p in l:
p.join()
stop = time.time()
print("run time is %s"%(stop-start))
测试进程和线程运行的时间