一、什么是线程&进程

1、进程 (Process)

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

  多进程多用于处理CPU密集型任务,例如排序、计算都是消耗cpu的

2、线程 (Thread)

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

  多线程多用于处理IO密集型任务频繁写入读出,cpu负责调度,消耗的是磁盘空间

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秒

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

电脑cpu有几核,那么只能同时运行几个线程。但是python的多线程,只能利用一个cpu的核心。因为Python的解释器使用了GIL的一个叫全局解释器锁,它不能利用多核CPU,只能运行在一个cpu上面,但是运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。

  python只有一个GIL,运行python时,就要拿到这个锁才能执行,在遇到I/O 操作时会释放这把锁。如果是纯计算的程序,没有 I/O 操作,解释器会每隔100次操作就释放这把锁,让别的线程有机会 执行(这个次数可以通sys.setcheckinterval来调整)同一时间只会有一个获得GIL线程在跑,其他线程都处于等待状态。

1、如果是CPU密集型代码(循环、计算等),由于计算工作量多和大,计算很快就会达到100,然后触发GIL的释放与在竞争,多个线程来回切换损耗资源,所以在多线程遇到CPU密集型代码时,单线程会比较快

2、如果是I\O密集型代码(文件处理、网络爬虫),开启多线程实际上是并发(不是并行),IO操作会进行IO等待,线程A等待时,自动切换到线程B,这样就提升了效率。

三、线程锁

python2中在多个线程同时修改一个数据的时候,可能会把数据覆盖,因此需要加线程锁 threading.Lock()。但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)

四、守护线程

setDaemon(True)只要主线程结束,那么子线程立即结束,不管子线程有没有运行完成

import threading,time
def run():
    time.sleep(2)
    print('hello')
for i in range(5):
    t = threading.Thread(target=run)
    t.setDaemon(True)#把子线程设置成为守护线程,主线程死了,守护线程也死
    t.start()
print('Done,运行完成')
time.sleep(3)#加上这句,子线程就能执行完成了

五、多进程

一个简单的多线程,multiprocessing.Process(target=run,args=(3,))

import threading,multiprocessing
def my():
    print('hello')
def run(num):
    for i in range(num):
        t = threading.Thread(target=my)
        t.start()
#多进程可以利用多核cpu
if __name__ == '__main__':#必须加这个才能启动多进程
    processes = []
    for i in range(2):
        p = multiprocessing.Process(target=run,args=(3,))#启动一个进程
        # args只有一个参数一定后面要加逗号
        p.start()
        processes.append(p)
    [p.join() for p in processes] #与线程用法一致

 



posted on 2018-06-05 19:53  忻冉然  阅读(225)  评论(0编辑  收藏  举报