Python学习笔记二十二_多线程与多进程

 一、什么是线程&进程

1、进程 (Process)

  是资源的集合。其实就是程序(qq进程)。对于操作系统来说一个任务就是一个进程,例如打开浏览器就启动了一个浏览器进程,打开word就启动了一个word进程。

2、线程 (Thread)

  是程序里面最小的执行单元。比如打开word,可以同时打字、拼写检查等,这些进程中的“子任务”就是线程。

3、线程是包含在一个进程里面的,一个进程可以有多个线程

4、一个进程里面默认有一个线程

5、主线程与子线程,一个程序默认有一个主线程,由主线程来启动子线程

二、多线程

1、一个简单的多线程,threading.Thread(target=方法)

import threading,time
def run():
    time.sleep(3)
    print('hello')
# for i in range(5): #串行,运行需要15s
#     run()
for i in range(5):#多线程,并行运行,3s
    t = threading.Thread(target=run)#实例化了一个线程
    t.start()

 下面举一个下载网页的例子,列举多线程的函数如何传参,需要用args

import threading,time,requests
urls = {
    '58':'http://www.58.com/',
    'haozu':'https://www.haozu.com/bj',
}def down_html(file_name,url):
    req = requests.get(url).content #content返回二进制结果
    open(file_name+'.html','wb').write(req) #需要加上.html

# 1、串行
# start_time = time.time()
# for k,v in urls.items():
#     down_html(k,v)
# end_time = time.time()
# run_time = end_time-start_time
# print('串行下载总共花了%s秒'%run_time) #2.31秒

#2、并行
start_time = time.time()
for k,v in urls.items():
    t = threading.Thread(target=down_html,args=(k,v))#多线程的函数如果传参的话,必须得用args
    t.start()
end_time = time.time()
run_time = end_time-start_time
print('并行下载总共花了%s秒'%run_time) #0.003秒

从这个下载网页的例子中看到,并行下载的时间远远短于串行,但事实真的是这样么?

  我们看到并行运行时是先打印出时间,但是程序未运行结束。实际上打印的时间是主线程结束时间,主线程结束后子线程还未结束,所以程序未结束运行。所以0.003s这个时间是主线程运行的时间,而不是并行下载的时间。如果想看到并行下载的时间,就需要引入线程等待。

2、线程等待,t.join()

import threading,time,requests
urls = {'58':'http://www.58.com/','haozu':'https://www.haozu.com/bj',}
def down_html(file_name,url):
    req = requests.get(url).content #content返回二进制结果
    open(file_name+'.html','wb').write(req) #需要加上.html

start_time = time.time()
threads = []
for k,v in urls.items(): #2个线程
    t = threading.Thread(target=down_html,args=(k,v))
    t.start()
    threads.append(t)
#实际有3个线程,进程里面默认有一个线程,这个线程叫做主线程
for t in threads:#主线程循环等待2个子线程执行结束
    t.join()#循环等待
end_time = time.time()
run_time = end_time-start_time
print('串行下载总共花了%s秒'%run_time) #0.56秒

有了线程等待,主线程就会等到子线程全部执行结束再结束,这样统计出的才是真正的并行下载时间。

这里又有了一个新的问题,如果我们想看到每个线程运行的时间怎么办呢,需要在down_html函数中打印。但若需要获得down_html函数返回值时,需要特殊处理,因为多线程调用函数时,函数的返回值是获取不到的。

 3、多线程调用函数时,如何获取函数返回值

 只能在函数外定义字典或者list来存储返回值

import threading,time,requests
urls = {'58':'http://www.58.com/','haozu':'https://www.haozu.com/bj',}
data = {}#多线程调用函数时,函数返回值获取不到,只能在函数外定义字典或者list来存储返回值
def down_html(file_name,url):
    start_time = time.time()
    req = requests.get(url).content
    open(file_name+'.html','wb').write(req)
    end_time = time.time()
    run_time = end_time - start_time
    print(run_time, url)  # 打印每个进程的耗时
    data[url] = run_time  # 定义字典存储返回值

start_time = time.time()
threads = []
for k,v in urls.items(): 
    t = threading.Thread(target=down_html,args=(k,v))
    t.start()
    threads.append(t)
for t in threads:#主线程循环等待2个子线程执行结束
    t.join()
end_time = time.time()
run_time = end_time-start_time
print('串行下载总共花了%s秒'%run_time) #0.56秒

三、线程锁

python2中在多个线程同时修改一个数据的时候,可能会把数据覆盖,因此需要加线程锁。但python3里面不加锁也无所谓,默认会自动帮你加锁。

import threading
num = 1
lock = threading.Lock()#实例化一把锁
#Python3默认会加锁,python2需要加锁,不然可能会覆盖以前的数据
def run():
    global num
    lock.acquire()  # 加锁
    num += 1
    lock.release()  # 解锁
for i in range(10):
    t = threading.Thread(target=run)
    t.start()
print(num)

四、守护线程

五、多进程

1、如果这个函数中有返回值,怎么获取
子线程运行的函数,如果里面有返回值的话,是不能获取到的
只能在外面定义一个list或者字典来存每次处理的结果
电脑cpu有几核,那么只能同时运行几个线程
但是python的多线程,只能利用一个cpu的核心
GIL全局解释器锁
2、锁,在多个线程同时修改一个数据的时候,可能会把数据覆盖,在python2里面需要加锁
python3里面不加锁也无所谓,默认会自动帮你加锁

在python里面,多线程被很多人诟病,为什么呢,因为Python的解释器使用了GIL的一个叫全局解释器锁,它不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。
作业1:自己查一下
为什么python的多线程不能利用多核cpu,但是在写代码的时候,多线程的确在并发,而且还比单线程快(博客)
3、守护线程
只要主线程结束,那么子线程立即结束,不管子线程有没有运行完成

多进程
多用于处理CPU密集型任务
例如排序、计算都是消耗cpu的
多线程
多用于处理IO密集型任务
频繁写入读出,cpu负责调度,消耗的是磁盘空间

猜你喜欢

转载自www.cnblogs.com/dongrui624/p/9117983.html