python3基础教程邓英_《莫烦Python3基础教程》学习笔记3

1.什么是多线程

多线程是加速程序计算的有效方式,Python的多线程模块threading上手快速简单

2.添加线程 Thread

import threading #导入模块

def main():

print(threading.active_count()) #获取已激活线程

print(threading.enumerate()) #查看所有线程信息

print(threading.current_thread()) #查看正在运行的线程

if __name__=='__main__':

main()

"""

1

[<_mainthread started>]

"""

def thread_job():

print('This is a thread of %s' % threading.current_thread())

def main():

added_thread=threading.Thread(target=thread_job) #添加一个线程,线程工作内容为target

added_thread.start() #开始运行线程

if __name__ == '__main__':

main()

"""

This is a added Thread,number is

"""

3.join功能

(1)不加join()的结果

def thread_job():

print('T1 start\n')

for i in range(10):

time.sleep(0.1) #任务间隔0.1秒,增加T1线程工作的耗时

print('T1 finish\n')

def main():

added_thread = threading.Thread(target=thread_job,name='T1')

added_thread.start() # 开始运行线程

print('all done\n')

if __name__=='__main__':

main()

#预想中的结果:

"""

T1 start

T1 finish

all done

"""

#实际结果

"""

all done

T1 start

T1 finish

"""

不加join()时的运行结果,线程任务还未完成就输出了all done,所以需要加入join

()功能来控制多个线程的执行顺序.

(2)加入join()后的结果

我们增加一个线程T2,T2的任务量较小,比T1更早完成,得到的结果如下:

def T1_job():

print('T1 start\n')

for i in range(10):

time.sleep(0.1) #任务间隔0.1秒

print('T1 finish\n')

def T2_job():

print('T2 start\n')

print('T2 finish\n')

thread1=threading.Thread(target=T1_job,name='T1') # 添加一个线程

thread2=threading.Thread(target=T2_job,name='T2')

thread1.start() # 开始运行线程

thread2.start()

print('all done\n')

"""

T1 start

T2 start

T2 finish

all done

T1 finish

"""

此时两个线程都未加入join,all done的出现完全取决于两个线程的执行速度,非常杂乱,所以需要使用join加以控制.

首先尝试在T1启动后,T2启动前加上thread1.join():

thread1.start()

thread1.join()

thread2.start()

print('all done\n')

"""

T1 start

T1 finish

T2 start

all done

T2 finish

"""

此时会发现,T2会在T1执行完之后再开始执行.

如果在T2启动后加上thread1.join():

thread1.start()

thread2.start()

thread1.join()

print('all done\n')

"""

T1 start

T2 start

T2 finish

T1 finish

all done

"""

T2在T1之后启动,并且因为T2任务量小,会在T1之前完成;而T1也因为加了join,all done在它完成后才显示。

加入thread2.join()的结果也可以继续尝试;而最好的方法是按照如下1221的排序:

thread1.start()

thread2.start()

thread2.join()

thread1.join()

print('all done\n')

"""

T1 start

T2 start

T2 finish

T1 finish

all done

"""

4.储存进程结果 Queue

多线程函数中定义一个Queue,用来保存返回值,代替return,定义一个多线程列表,初始化一个多维数据列表,用来处理:

from queue import Queue

#定义一个被多线程调用的函数

def job(l,q): #函数参数是一个列表l和一个队列q

for i in range(len(l)):

l[i]=l[i]**2 #平方计算

q.put(l) #多线程调用的函数不能用return返回值

#定义一个多线程函数

def multithreading():

q=Queue() #q中存放返回值,代替return

threads=[]

data=[[1,2,3],[3,4,5],[4,4,4],[5,5,5]]

for i in range(4): #定义4个线程

t=threading.Thread(target=job,args=(data[i],q))

t.start() #开始线程

threads.append(t) #把每个线程append到线程列表中

for thread in threads: #分别join四个线程到主线程

thread.join()

results=[] #定义一个空列表results,将四个线程运行后保存在队列中的结果返回给空列表

for _ in range(4):

results.append(q.get()) #q.get()按顺序从q中拿出一个值

print(results)

if __name__ =='__main__':

multithreading()

"""

[[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]

"""

5.GIL 不一定有效率

在python中,无论有多少个核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。Global Interpreter Lock (GIL)的全称是全局解释器锁,来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看做是“通行证”,并且在一个python进程中,GIL只有一个,拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,而只能利用GIL保证同一时间只能有一个线程拿到数据。

在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。

下面来测试GIL:

我们创建一个 job, 分别用 threading 和 一般的方式执行这段程序. 并且创建一个 list 来存放我们要处理的数据. 在 Normal 的时候, 我们这个 list 扩展4倍, 在 threading 的时候, 我们建立4个线程, 并对运行时间进行对比.

import threading

from queue import Queue

import copy

import time

def job(l,q):

res=sum(l)

q.put(res)

def multithreading(l):

q=Queue()

threads=[]

for i in range(4):

t=threading.Thread(target=job,args=(copy.copy(l),q),name='T%i'%i)

t.start()

threads.append(t)

[t.join()for t in threads]

total=0

for _ in range(4):

total+=q.get()

print(total)

def normal(l):

total=sum(l)

print(total)

if __name__=='__main__':

l=list(range(1000000))

s_t=time.time()

normal(l*4)

print('normal:',time.time()-s_t)

s_t=time.time()

multithreading(l)

print('multithreading:',time.time()-s_t)

"""

1999998000000

normal: 0.20466065406799316

1999998000000

multithreading: 0.18656229972839355

"""

由输出结果可以知道,程序 threading 和 Normal 运行了一样多次的运算. 但是我们发现 threading 却没有快多少, 按理来说, 我们预期会要快3-4倍, 因为有建立4个线程, 但是并没有. 这就是其中的 GIL 在作怪.

6.Lock 线程锁

(1)不使用Lock的情况

#全局变量A的值每次加1,循环10次,并打印

def job1():

global A

for i in range(10):

A+=1

print('job1',A)

#全局变量A的值每次加10,循环10次,并打印

def job2():

global A

for i in range(10):

A+=10

print('job2',A)

#定义两个线程,分别执行job1和job2

if __name__=='__main__':

A=0 #全局变量

t1=threading.Thread(target=job1)

t2=threading.Thread(target=job2)

t1.start()

t2.start()

t1.join()

t2.join()

"""

job1 1

job1 2

job1job2 13

job2 23

job2 3

job1 33

job2 44

job2 54

job2 64

job2 74

job2 3484

job1 85

job2job1 96

95

job2job1 106107

job1 108

job1 109

job1 110

"""

此时的打印结果十分混乱.

(2)使用lock的情况

#给函数一和函数二加锁

def job1():

global A,lock

lock.acquire() #将共享内存上锁,确保当前线程不会被其他线程访问

for i in range(10):

A+=1

print('job1',A)

lock.release() #将锁打开,保证其他线程可以使用该共享内存

def job2():

global A,lock

lock.acquire()

for i in range(10):

A+=10

print('job2',A)

lock.release()

#在主函数中定义一个Lock

if __name__=='__main__':

lock=threading.Lock()

A=0 #全局变量

t1=threading.Thread(target=job1)

t2=threading.Thread(target=job2)

t1.start()

t2.start()

t1.join()

t2.join()

"""

job1 1

job1 2

job1 3

job1 4

job1 5

job1 6

job1 7

job1 8

job1 9

job1 10

job2 20

job2 30

job2 40

job2 50

job2 60

job2 70

job2 80

job2 90

job2 100

job2 110

"""

由结果可知,使用lock后,一个一个线程执行完。

1.什么是 Multiprocessing

多进程 Multiprocessing 和多线程 threading 类似, 他们都是在 python 中用来并行运算的;Multiprocessing 可以用来弥补 threading 的一些劣势, 比如在 threading中提到的GIL.

2.添加进程 Process

线程和进程十分相似,对比如下:

import multiprocessing as mp

import threading as td

#定义一个被线程和进程调用的函数

def job(a,b):

print('aaaaa')

if __name__=='__main__': #运用时需要添加上一个定义main函数的语句

#创建线程和进程

t1 = td.Thread(target=job, args=(1, 2))

p1=mp.Process(target=job,args=(1,2))

#启动线程和进程

t1.start()

p1.start()

#连接线程和进程

t1.join()

p1.join()

注:join的用法与线程完全相同可参考之前的内容.

3.存储进程输出 Queue

多线程调用的函数不能有返回值,使用Queue存储多个线程运算的结果.

import multiprocessing as mp

#定义一个被多线程调用的函数

def job(q):

res=0

for i in range(1000):

res+=i+i**2+i**3

q.put(res)

#定义一个多线程队列,用来存储结果

if __name__=='__main__':

q=mp.Queue()

p1=mp.Process(target=job,args=(q,)) #注意q之后的逗号,表示args是可迭代的

p2=mp.Process(target=job,args=(q,))

p1.start()

p2.start()

p1.join()

p2.join()

res1=q.get()

res2=q.get()

print(res1+res2)

"""

499667166000

"""

4.效率对比 threading & multiprocessing

我们来对比下多进程,多线程和什么都不做时的消耗时间,看看哪种方式更有效率.

import multiprocessing as mp

import threading as td

import time

def job(q):

res = 0

for i in range(1000000):

res += i + i ** 2 + i ** 3

q.put(res)

#创建多进程

def multicore():

q = mp.Queue()

p1 = mp.Process(target=job, args=(q,))

p2 = mp.Process(target=job, args=(q,))

p1.start()

p2.start()

p1.join()

p2.join()

res1 = q.get()

res2 = q.get()

print('multicore:',res1 + res2)

#创建普通函数

def normal():

res = 0

for _ in range(2): #建立了两个进程和线程,所以normal()中循环两次

for i in range(1000000):

res += i + i ** 2 + i ** 3

print('normal:',res)

#创建多线程

def multithread():

q = mp.Queue()

t1 = td.Thread(target=job, args=(q,))

t2 = td.Thread(target=job, args=(q,))

t1.start()

t2.start()

t1.join()

t2.join()

res1 = q.get()

res2 = q.get()

print('multithread:',res1 + res2)

#定义运行时间函数

if __name__=='__main__':

st=time.time()

normal()

st1=time.time()

print('normal time:',st1-st)

multithread()

st2=time.time()

print('multithread time:',st2-st1)

multicore()

print('multicore time:',time.time()-st2)

"""

normal: 499999666667166666000000

normal time: 1.3301937580108643

multithread: 499999666667166666000000

multithread time: 1.2966506481170654

multicore: 499999666667166666000000

multicore time: 1.1172370910644531

"""

普通/多线程/多进程的运行时间分别是1.33,1.29和1.11秒。 我们发现多核/多进程最快,说明在同时间运行了多个任务。

将运算次数加十倍,结果如下:

"""

normal: 4999999666666716666660000000

normal time: 13.460938453674316

multithread: 4999999666666716666660000000

multithread time: 12.880556583404541

multicore: 4999999666666716666660000000

multicore time: 8.716708898544312

"""

这次运行时间依然是 多进程 < 多线程 < 普通,由此我们可以清晰地看出哪种方法更有效率。

5.进程池 Pool

进程池就是把所有需要运行的东西放到一个池子中,然后python自己解决如何分配进程,如何分配运行结果等问题. Pool和之前的Process的不同点是丢向Pool的函数有返回值,而Process的没有返回值。

import multiprocessing as mp

def job(x):

return x*x

def multicore():

#定义一个pool,默认参数是CPU的核数,传入processes参数定义CPU核数为2

pool=mp.Pool(processes=2)

res=pool.map(job,range(10)) #把函数和需要迭代运算的值map到一起,返回结果

print(res)

#apply_async():一次只能传入一个值,只会放入一个核进行运算

res=pool.apply_async(job,(2,)) #注意加逗号,传入值是可迭代的

print(res.get()) #用get获取结果

#用 apply_async() 输出多个结果

multi_res=[pool.apply_async(job,(i,))for i in range(10)] #将apply_async()放入迭代器中

print([res.get()for res in multi_res]) #从迭代器中取出来

if __name__=='__main__':

multicore()

"""

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] #map()

4 #apply_aysnc()

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] #multi_res

"""

由运行结果可知,map() 放入迭代参数,返回多个结果;apply_async()只能放入一组参数,并返回一个结果,如果想得到map()的效果需要通过迭代.

6.共享内存 shared memory

使用共享内存才能让CPU之间有交流.

(1)使用Value数据存储在一个共享的内存表中.

import multiprocessing as mp

value1 = mp.Value('i', 0)

value2 = mp.Value('d', 3.14)

其中d和i参数用来设置数据类型的,d表示一个双精浮点类型,i表示一个带符号的整型。更多的形式请参考:https://docs.python.org/3/library/array.html

(2)Shared Array

array=mp.Array('i',[1,3,4]) #与numpy的有区别,这里的array只是一个列表,不能是多维的

7.进程锁 Lock

与线程锁的使用相似.

(1)不使用Lock的情况

def job(v,num):

for _ in range(5):

time.sleep(0.1) #暂停0.1秒,让输出效果更明显

v.value+=num #获取共享变量值

print(v.value)

def multicore():

v=mp.Value('i',0) #定义共享变量

p1=mp.Process(target=job,args=(v,1))

p2=mp.Process(target=job,args=(v,3)) #设定不同的number看如何抢夺内存

p1.start()

p2.start()

p1.join()

p2.join()

if __name__=='__main__':

multicore()

"""

1

4

5

8

9

12

13

16

17

20

"""

我们可以看到,进程1和进程2在相互抢着使用共享内存v.

(2)使用Lock的情况

def job(v,num,l):

l.acquire() #锁住

for _ in range(5):

time.sleep(0.1) #暂停0.1秒,让输出效果更明显

v.value+=num #获取共享变量值

print(v.value)

l.release() #释放

def multicore():

l=mp.Lock() #定义一个进程锁

v=mp.Value('i',0) #定义共享变量

p1=mp.Process(target=job,args=(v,1,l)) #将Lock传入

p2=mp.Process(target=job,args=(v,3,l))

p1.start()

p2.start()

p1.join()

p2.join()

if __name__=='__main__':

multicore()

"""

1

2

3

4

5

8

11

14

17

20

"""

由上面的结果可知,进程锁保证了进程p1的完整运行,然后才进行了进程p2的运行.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值