zoukankan      html  css  js  c++  java
  • Python学习笔记二十二_多线程与多进程

     一、什么是线程&进程

    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、如果是IO密集型代码(文件处理、网络爬虫),开启多线程实际上是并发(不是并行),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] #与线程用法一致



  • 相关阅读:
    矩阵图
    博客园评价
    团队冲刺
    团队冲刺
    第二阶段团队冲刺
    团队博客
    团队冲刺
    总结会议
    会议10
    会议09
  • 原文地址:https://www.cnblogs.com/dongrui624/p/9117983.html
Copyright © 2011-2022 走看看