python基础:多进程、多线程

一、定义和区别

1、一个任务就是一个进程,进程就是资源的集合。比如打开浏览器,启动一个进程。当一个进程需要干很多事的时候,就需要执行多个子任务,这些子任务就是线程。

2、线程是包含在进程中的,每个进程至少有一个默认的线程(主线程),可以有多个线程

3、进程默认有个主线程。而其他子线程则是由主线程启动的。

4、通过线程运行的函数无法return值,如果需要函数返回值,需要专门定义一个list或者字典等来接收

5、多线程运行,一般电脑cpu有几核,就可以同时运行几个线程。但是python中,多线程只能运行一个cpu内核。

 因为python中有一个GIL全局解释器,运行python时必须要拿到这把锁才可以执行。遇到I/O操作才会释放。锁只有一把,所以同一时刻也只会有一个线程运行一个cpu内核。那么再写代码运行的时候,为什么有时候多线程看起来确实实在并发,而且比单线程快??请看文章最后

6、多个线程同时修改一个数据时,可能会把数据覆盖。在python2中需要手动加一把锁。但是在python3中会自动加锁

二、多线程

多线程需要引入threading模块:import threading

1、多线程的操作举例:

扫描二维码关注公众号,回复: 1481507 查看本文章
def run():
    time.sleep(3)
    print('哈哈哈')


for i in range(5):
    t=threading.Thread(target=run)#开启一个线程,用target参数指定要运行的函数,target后跟函数名,不加括号。
    t.start()#启动线程

运行结果:

从上面的运行结果可以看出。5个线程是同时运行的。

当线程运行的函数需要传递参数时,使用如下:

import threading,time
def run(str):
    time.sleep(3)
    print('哈哈哈%s:%s'%(time.time(),str))


for i in range(5):
    t=threading.Thread(target=run,args=(5,))#用args传递参数。注意的是如果参数只有1个,必须加逗号
    t.start()

运行结果:

2、线程等待

比如我们来计算多线程执行的时间,代码如下

import threading,time
def run():
    time.sleep(3)
    print('哈哈哈哈')
start_time=time.time()#记录开始时间
for i in range(5):
    t=threading.Thread(target=run)
    t.start()
end_time=time.time()#记录结束时间
print('run_time...',end_time-start_time)#结束时间-开始时间得到运行时间

运行结果:

可以看到,运行时间非常小,这肯定是不正确的。毕竟每个线程运行前都有个time.sleep(3)等待3秒,那么多线程运行的时间必定至少得有3秒。那么上述问题在哪里呢?这就关系到我们最开始说的那个默认主线程了。整个python代码运行时是一个进程,默认的有一个主线程。我们开启的5个线程都是由主线程启动的。当主线程把5个子线程自动以后,它就自动往下运行,执行end_time=time.time()了。而不管子线程的运行是否结束。所以最终这个时间计算的是主线程运行的时间。而不是所有线程执行完的时间。

如果想要计算多线程运行时间,那么就要使用线程等待。让主线程等,等到子线程都运行完了以后,再统计结束时间。。线程等待用join().代码修改如下:

import threading,time
def run():
    time.sleep(3)
    print('哈哈哈哈')

start_time=time.time()
threads=[]#存放启动的五个线程
for i in range(5):
    t=threading.Thread(target=run)
    t.start()
    threads.append(t)#把每个线程都加入到list中保存起来

print('threads,,,,',threads)#可以查看list中保存的5个线程
for t in threads:#主线程循环等待五个子线程
    t.join()#等待线程执行结束....虽然是循环挨个等待,但是每个线程都是同时进行的,可能当你等第一个的时候,第二个线程都已经执行完了
    #在循环第二个的时候,不用等了直接跳第三个。如果第三四个都执行完了,相当于只等待了第一个执行结束的时间。
#等全部结束了,然后会接着执行下面代码
end_time=time.time()#这个时间就是所有子线程运行结束的时间 print('run_time...',end_time-start_time)

运行结果:

3、守护线程

只要主线程结束,那么子线程立即结束。不管子线程有没有运行完。守护线程的定义:t.setDaemon(True)

示例:

import threading,time
def run():
    time.sleep(3)
    print('哈哈哈')
for i in range(50):
    t=threading.Thread(target=run)
    t.setDaemon(True)#把子线程设置成为守护线程,当主线程死了,守护线程也死
    t.start()

print('done....运行完毕')

运行结果:

可以看到,并没有打印‘哈哈哈’。这是因为,主线程启动50个线程,然后这50个线程执行run函数的时候,都要必须先time.sleep(3)后才会执行print('哈哈哈')’。但是主线程是继续执行的,很快主线程就执行完了,打印‘done....运行完毕’.因为子线程是主线程的守护线程,所以主线程执行完以后,子线程还没来得及打印就跟着主线程一块死了

4、简单的爬虫示例

爬虫就是抓取网页的数据,保存到本地。然后可以对这些数据进行分析。做其他操作

需求:抓取网页数据,并保存到本地为html格式
urls={'11':'http://www.11.cn', '22':'http://www.22.cn', '33':'http://www.33.cn', '44':'http://www.44.cn'}#爬虫的网址 dic={}#获取每个网址下载并保存的时间 import requests,time def down_html(filename,url): start_time=time.time() req=requests.get(url).content#打开url, .content表示获取url内容返回文件为二进制格式。 open(filename+'.html','wb').write(req)#将抓取到的数据写入一个新的html文件中 end_time=time.time() print(end_time-start_time,url) dic[url]=end_time-start_time#用一个字典获取结果 #并行 threads=[] start_time=time.time() for k,v in urls.items():#循环5次,5个线程启动, t=threading.Thread(target=down_html,args=(k,v))#多线程传参必须用args传参。target的值是函数名,不加括号 #args如果只有一个参数,必须加一个逗号 t.start() threads.append(t) for t in threads:#循环等待子线程运行完 t.join() end_time=time.time() run_time=end_time-start_time print('下载总共花了%s时间'%run_time)

三、多进程

多进程要引入multiprocessing模块,import multiprocessing

示例:

import multiprocessing,threading
def my():
    print('哈哈哈哈')
def run(num):

    for i in range(num):
        t=threading.Thread(target=my)
        t.start()

if __name__=='__main__':#多进程必须写这个
    for p in range(5):
        p=multiprocessing.Process(target=run,args=(6,))#进程,args如果只有一个参数,必须加一个逗号
        p.start()

四、线程锁

当多个线程需要修改同一个数据时,需要加上线程锁,防止数据被乱改。python2中需要加,python3中不需要。会自动给加

定义一个锁:lock=threading.Lock()

在操作的代码前加锁:lock.aquire()

在操作代码后解锁:lock.release()

示例:

import threading,time
num=1
lock=threading.Lock()
def run():
    time.sleep(1)
    global num
    lock.acqurie()#加锁,python3中不加也可以,会默认加
    num+=1
    lock.release()#解锁
ts=[]
for i in range(50):
    t=threading.Thread(target=run)
    t.start()
    ts.append(t)
[t.join() for t in ts]#列表生成式,循环等待所有线程结束
print(num)

五、为什么python的多线程不能利用多核CPU,但是咱们在写代码的时候,多线程的确是在并发,而且还比单线程快

原因:因为GIL,python只有一个GIL,运行python时,就要拿到这个锁才能执行,在遇到I/O 操作时会释放这把锁。
如果是纯计算的程序,没有 I/O 操作,解释器会每隔100次操作就释放这把锁,让别的线程有机会 执行(这个次数可以通sys.setcheckinterval
来调整)同一时间只会有一个获得GIL线程在跑,其他线程都处于等待状态
1、如果是CPU密集型代码(循环、计算等),由于计算工作量多和大,计算很快就会达到100,然后触发GIL的释放与在竞争,多个线程来回切换损耗资源,
所以在多线程遇到CPU密集型代码时,单线程会比较快
2、如果是I\O密集型代码(文件处理、网络爬虫),开启多线程实际上是并发(不是并行),IO操作会进行IO等待,线程A等待时,自动切换到线程B,
这样就提升了效率

总结:cpu密集型,单线程快;i/o密集型,多线程快

猜你喜欢

转载自www.cnblogs.com/bendouyao/p/9142796.html