在进入主题之前,我们先学习一下并发和并行的概念:

--并发:在操作系统中,并发是指一个时间段中有几个程序都处于启动到运行完毕之间,且这几个程序都是在同一个处理机上运行。但任一时刻点上只有一个程序在处理机上运行。形象的点描述:一个人做很多事情,但同一时刻只能做一件事情。

--并行:当系统有一个CPU时,则程序的操作有可能非并发。当一个CPU执行一个程序时,另一个CPU可以执行另一个程序,两个程序互不抢占CPU资源,可以同时进行。形象的描述:多人同时做多件事情

可能有小伙伴又会问串行又是什么鬼呢?再来唠叨一下:

串行和并行是数据传输方式的区别。其实和并发和并行的概念是两个领域:

1、数据传输方式不同,串行口传输方式为数据排成一行,一位一位送出,接受也是一样。并行口传输8位数据一次送出。

2、针脚不同,串行口针脚少,并行口针脚多

3、用途不同,串行口现在只用作控制接口,并行口多用作打印机、扫描仪等接口。

来吧,进入正题:

进程的概念:计算机程序其实只是存储在磁盘上可执行的二进制(或其他可执行的类型)文件。只有把它们加载到内存中并被操作系统调用,才算是被执行,并用拥有生命周期。所以说进程是一个执行中的程序。

每个进程都拥有自己的地址空间,内存,数据栈以及其他用于跟踪执行的辅助数据。

此间,操作系统管理其上的所有进程的执行,并为这些进程合理分配时间。

多进程:那就是在一个多核的电脑上同时运行多个程呗,其实是实现并行。

但是呢,我们先从多线程学习起。多线程搞定了,多进程也就是小意思了。

在此之前先介绍一个概念,之后会用到。GIL锁,有可能大家会听到别人说python运行很慢啥的,其实就是这个GIL锁的原因,那它到底是个啥呢:

GIL全局解释性锁,python在设计的时候,还没有多核处理器的概念。因此,为了设计的方便和线程的安全,python之父就设计了一个锁。这个锁要求,任何一个进程中,同时只能有一个线程在执行。因此并不能多个线程分配多个CPU资源。所以python中的线程只能实现并发,而不能实现真正的并行,这就是python所线程的局限性。在python3中GIL锁进行了该井,在遇到任何IO阻塞(不是耗时)的时候,会自动切换线程,这就使得在进行多IO操作的时候python的锁多线程有很大的优势。同时一个线程在运行时间或者运行步骤达到一定的阈值时,也会自动的切换线程。

对比于多进程,多线程其实是实现并发的操作,在在python中使用multiprocessing包实现多进程:

首先来看下多线程的实现,这里使用的是threading包。用sleep模拟耗时操作:

  1. import time
  2. import threading
  3.  
  4. def get_detail_html(url):
  5. print("starting get detail html")
  6. time.sleep(4)
  7. print("ending get detail html")
  8.  
  9. def get_detail_url(url):
  10. print("starting get detail url")
  11. time.sleep(2)
  12. print("ending get detail url")
  13.  
  14. if __name__ == '__main__':
  15. thread1 = threading.Thread(target=get_detail_html, args=("",))
  16. thread2 = threading.Thread(target=get_detail_url, args=("",))
  17. start_time = time.time()
  18. thread1.setDaemon(True)
  19. thread2.setDaemon(True)
  20. thread1.start()
  21. thread2.start()
  22. print(123456)
  23. thread1.join()
  24. thread2.join()
    25    print("using time: {}".format(time.time()-start_time)

做个注释吧,可能有点乱:

1、在子线程sleep时候,主线程继续往下执行,主线程执行完,程序此刻并没有退出。子线程的程序还是会执行完。

2、为了在主线程执行完之后kill掉子线程。需要给子线程.setdeDaemon(True)。将子线程设置为守护线程。意思为:主线程结束后设置为守护线程的子线程也会义无反顾的殉情而亡当有多个子线程,其中部分设置守护线程。主线程会等到没有设置为守护线程的子线程运行完之后推出而设置为守护线程的子线程,如果在未设置为守护线程的子线程结束前运行完毕,那万事大吉,如果没有运行完成还是跟着主线程殉情

3、如果想让主线程等待子线程执行完成再往下面执行的话,用thread1.join()。将主线程阻塞住,也就是说.join()之后的主线程代码不会执行,直到子线程执行完毕再执行这个时候的using time 就是4秒钟。如果只有部分子线程阻塞,那么阻塞时间就是设置了.join()的子线程运行的时间。然后主线程结束,但是还是会等到所有子线程运行结束后主线程才会退出

上边是使用函数的方法实现多线程,那用类呢?可定也能实现咯:

  1. import time
    import threading
  2.  
  3. # 用类实现多线程
  4. class GetDetailHtml(threading.Thread):
  5. """
  6. 继承threading.Thread类
  7. """
  8. def __init__(self, name):
  9. # 给调用父类init方法线程命名
  10. super().__init__(name=name)
  11. def run(self):
  12. """
  13. 重写run方法
  14. :return:
  15. """
  16. print("starting get detail html")
  17. time.sleep(4)
  18. print("ending get detail html")
  19. class GetDetailUrl(threading.Thread):
  20. def __init__(self, name):
  21. # 给调用父类init方法线程命名
  22. super().__init__(name=name)
  23. def run(self):
  24. print("starting get detail url")
  25. time.sleep(2)
  26. print("ending get detail url")
  27. if __name__ == '__main__':
  28. thread1 = GetDetailHtml('get_detail_html')
  29. thread2 = GetDetailUrl('get_detail_url')
  30. start_time = time.time()
  31. thread1.start()
  32. thread2.start()
  33. thread1.join()
  34. thread2.join()
  35. print('used time: {}'.format(time.time() - start_time))

只需要重写类的run方法即可,其他和函数方法使用一样。

如果多个线程需要用到相同的数据咋办呢?这就涉及到线程间的通信问题了,接下来我们来详细的了解一下。

方法一:利用共享变量的方式,嗯,也就是全局变量global。

  1. import time
  2. import threading
  3. # 1、共享全局变量 使用global。
  4. detail_url_list = []
  5. def get_detail_html():
  6. global detail_url_list
  7. # 使用for循环,这样子做得话并发不高
  8. # for url in detail_url_list:
  9. # print("starting get detail html")
  10. # time.sleep(4)
  11. # print("ending get detail html")
  12. # 使用pop操作,并开启多个get_detail_html的子线程进行处理。但是线程是不安全的
  13. url = detail_url_list.pop()
  14. print("starting get detail html")
  15. time.sleep(4)
  16. print("ending get detail html")
  17.  
  18. def get_detail_url():
  19. global detail_url_list
  20. print("starting get detail url")
  21. time.sleep(2)
  22. for i in range(20):
  23. detail_url_list.append(i)
  24. print("ending get detail url")
  25.  
  26. if __name__ == '__main__':
  27. thread1 = threading.Thread(target=get_detail_html, args=("",))
  28. thread2 = threading.Thread(target=get_detail_url, args=("",))
  29. start_time = time.time()
  30. thread1.start()
  31. thread2.start()thread1.join()
  32. thread2.join()
  33. print("using time: {}".format(time.time() - start_time))

这个很容易理解,用起来也很方便。可以帮我们快速的解决比较简单的问题。

方法二:利用队列queue。。比价高级点的用法。先看实现方式。

  1. import time
  2. import threading
  3. from queue import Queue, PriorityQueue #可以设置优先级的queue,调整执行顺序
  4.  
  5. def get_detail_html(queue):
  6. while True:
  7. url = queue.get()
  8. print("starting get detail html")
  9. time.sleep(4)
  10. print("ending get detail html")
  11.  
  12. def get_detail_url(queue):
  13. while True:
  14. print("starting get detail url")
  15. time.sleep(2)
  16. for i in range(20):
  17. queue.put(i)
  18. print("ending get detail url")
  19.  
  20. if __name__ == '__main__':
  21. detail_url_queue = Queue(maxsize=10)
  22. thread1 = threading.Thread(target=get_detail_html, args=(detail_url_queue,))
  23. thread2 = threading.Thread(target=get_detail_url, args=(detail_url_queue,))
  24. start_time = time.time()
  25. thread1.start()
  26. thread2.start()thread1.join()
  27. thread2.join()
  28. print("using time: {}".format(time.time() - start_time))

简单的说就是:put往queue里放东西,get从queue里拿东西。这样就能达到线程间的通信以及线程安全。

既然线程中的数据大家都能用,会不会出现一个线程在修改数据,结果在还没有修改完之后,GIL锁已经被切换。切换后的线程也修改这个数据,那就会出现数据的错乱,脏数据。针对这种情况,python中的lock就完美的解决了这个情况。

  1. from threading import Lock, RLock #可重入锁
  2. #RLock使得一个线程中,可以连续调用多次acquire,但是的有相对应数量的release
  3. import threading
  4.  
  5. total = 0
  6. lock = Lock()
  7. """
  8. 1、用锁会影响性能
  9. 2、锁会引起死锁 1、acquire之后没有release
  10. 2、线程相互等待 两个数据被两个线程分别获取,相互等待对方的资源释放
  11. """
  12. def add(lock):
  13. global total
  14. for i in range(1000000):
         # 上锁
  15. lock.acquire()
  16. total += 1
         # 释放锁
  17. lock.release()
  18.  
  19. def desc(lock):
  20. global total
  21. for i in range(1000000):
         # 上锁
  22. lock.acquire()
  23. total -= 1
    # 释放锁
  24. lock.release()
  25. thread1 = threading.Thread(target=add, args=(lock,))
  26. thread2 = threading.Thread(target=desc, args=(lock,))
  27. thread1.start()
  28. thread2.start()
  29. thread1.join()
  30. thread2.join()
  31. print(total)

针对一般情况下我们lock就可以搞定问题,那有没有更牛逼的工具让我们应对更加复杂的情况呢?答案当然是肯定的:

conditon:条件变量,用于复杂的线程间同步的锁

  1. import threading
  2. # condition(条件变量)是用于复杂的线程间同步的锁
  3. from threading import Condition
  4.  
  5. cond = Condition()
  6. class XiaoAi(threading.Thread):
  7. def __init__(self, cond):
  8. super(XiaoAi, self).__init__(name="小爱")
  9. self.cond = cond
  10.  
  11. def run(self):
  12. # with是一个魔法方法,相当于先上锁操作完之后再解锁,和with open相似
  13. with self.cond:
  14. # 等待状态,等着接受通知,接到通知后往下放执行
  15. self.cond.wait()
  16. print("{} : 在".format(self.name))
  17. # 发送通知
  18. self.cond.notify()
  19. # 等待状态,等着接受通知,接到通知后往下放执行
  20. self.cond.wait()
  21. print("{} : 好啊".format(self.name))
  22.  
  23. class TianMao(threading.Thread):
  24. def __init__(self, cond):
  25. super(TianMao, self).__init__(name="天猫")
  26. self.cond = cond
  27.  
  28. def run(self):
  29. with self.cond:
  30. print("{} : 小爱在么?".format(self.name))
  31. # 发送通知
  32. self.cond.notify()
  33. # 等待状态,等着接受通知,接到通知后往下放执行
  34. self.cond.wait()
  35.  
  36. print("{} : 我们来对古诗吧!".format(self.name))
  37. # 发送通知
  38. self.cond.notify()
  39.  
  40. if __name__ == '__main__':
  41. xiaoai = XiaoAi(cond)
  42. tianmao = TianMao(cond)
  43.  
  44. xiaoai.start()
  45. tianmao.start()
  46.  
  47. xiaoai.join()
  48. tianmao.join()

在condition中我们可以运行任意多的线程。那么如果我们想控制线程的数量该怎么办呢?针对这个场景python也是给我们一个工具的:

semaphore 是控制进入数量的锁, 基于condition实现

  1. # semaphore 是控制进入数量的锁, 基于condition实现
  2. # 文件的读写,写一般只有一个线程写,读可以允许多个
  3.  
  4. # 爬虫,控制爬虫的数量
  5. import threading
  6. import time
  7.  
  8. class HtmlSpider(threading.Thread):
  9. def __init__(self, url, sem):
  10. super().__init__()
  11. self.url = url
  12. self.sem = sem
  13.  
  14. def run(self):
  15. time.sleep(2)
  16. print('get html text success')
  17. # 释放锁, 一共释放3次
  18. self.sem.release()
  19. class UrlProducer(threading.Thread):
  20. def __init__(self, sem):
  21. super().__init__()
  22. self.sem = sem
  23. def run(self):
  24. for i in range(20):
  25. # 调用一次,次数减一,为0时,线程锁住
  26. self.sem.acquire()
  27. html_thread = HtmlSpider("https://baidu.com/{}".format(i), self.sem)
  28. html_thread.start()
  29. if __name__ == '__main__':
  30. sem = threading.Semaphore(3)
  31. url_producer = UrlProducer(sem)
  32. url_producer.start()

是不是常听说线程池?那这个池子到底是个啥呢?当然是放线程的池子呗!!在python中是用

  1. ThreadPoolExecutor来实现线程池的!!
    第一种用法:
  1. from concurrent.futures import (
  2. wait, # 使主线程阻塞
  3. ThreadPoolExecutor, # 线程池
  4. as_completed, #是一个生成器
  5. Future # 未来对象, task的返回容器
  6. )
  7.  
  8. """
  9. 线程池
  10. 主线程中可以获取某一个线程的状态或者某一个任务的状态,以及返回值
  11. 当一个线程完成的时候主线程能够立刻知道
  12. futures可以让多线程和多进程接口一致
  13. """
  14. import time
  15.  
  16. def get_html(times):
  17. time.sleep(times)
  18. print('get page {} success'.format(times))
  19. return times
  20.  
  21. executor = ThreadPoolExecutor(max_workers=2)
  22.  
  23. # 通过submit函数提交的函数到线程池中,submit是立刻返回的
  24. task1 = executor.submit(get_html, (3))
  25. task2 = executor.submit(get_html, (2))
  26.  
  27. # done 用于判定某个任务是否完成
  28. print(task1.done())
  29. # cancle取消该线程,取消成功返回True,失败返回False 注意:线程在执行过程中不能被取消
  30. # 此时开启两个线程,所以task1和task2添加之后立刻运行,因此取消不成功
  31. print(task2.cancel())
  32. time.sleep(3)
  33. print(task1.done())
  34.  
  35. # 可以获取task1的执行结果
  36. print(task1.result())
  37.  
  38. """
  39. # 通过executor 的map(yield task的返回值)获取已经完成的task值
  40. for data in executor.map(get_html, urls):
  41. print('future get {} page'.format(data))
  42. """

第二种用法:

  1. from concurrent.futures import (
  2. wait, # 使主线程阻塞
  3. ThreadPoolExecutor, # 线程池
  4. as_completed, #是一个生成器
  5. Future # 未来对象, task的返回容器
  6. )
  7. import time
  8.  
  9. def get_html(times):
  10. time.sleep(times)
  11. print('get page {} success'.format(times))
  12. return times
  13. executor = ThreadPoolExecutor(max_workers=2)
  14.  
  15. # 获取已经成功的task
  16. urls = [3, 3, 2, 4, 8]
  17. # 添加到excutor之后子线程立刻开始执行
  18. all_tasks = [executor.submit(get_html, (url)) for url in urls]
  19. # 使主线程阻塞,所有子线程执行完成后主线程再往下边执行
  20. wait(all_tasks)
  21. print(12314123)
  22. # as_completed 的代码块和子线程的执行并不是同步的
  23. for future in as_completed(all_tasks):
  24. data = future.result()
  25. print('get {} page 111111'.format(data))

第三种用法:

  1. from concurrent.futures import (
  2. wait, # 使主线程阻塞
  3. ThreadPoolExecutor, # 线程池
  4. as_completed, #是一个生成器
  5. Future # 未来对象, task的返回容器
  6. )
  7. import time
  8.  
  9. def get_html(times):
  10. time.sleep(times)
  11. print('get page {} success'.format(times))
  12. return times
  13.  
  14. executor = ThreadPoolExecutor(max_workers=2)
  15. # 通过executor 的map(yield task的返回值)获取已经完成的task值
  16. for data in executor.map(get_html, urls):
  17. print('future get {} page'.format(data))

好了多线程就讲这么多吧,有点粗糙,更多的细节以后再补充吧! 下一篇记录一下多进程!!

python的多线程和多进程(一)的更多相关文章

  1. Python之多线程和多进程

    一.多线程 1.顺序执行单个线程,注意要顺序执行的话,需要用join. #coding=utf-8 from threading import Thread import time def my_co ...

  2. Python的多线程和多进程

    (1)多线程的产生并不是因为发明了多核CPU甚至现在有多个CPU+多核的硬件,也不是因为多线程CPU运行效率比单线程高.单从CPU的运行效率上考虑,单任务进程及单线程效率是最高的,因为CPU没有任何进 ...

  3. Python【多线程与多进程】

    import time,threading print("=======串行方式.并行两种方式调用run()函数=======")def run(): print('哈哈哈') # ...

  4. python的多线程、多进程代码示例

    python多进程和多线程的区别:python的多线程不是真正意义上的多线程,由于python编译器的问题,导致python的多线程存在一个PIL锁,使得python的多线程的CPU利用率比预期的要低 ...

  5. selenium +python之多线程与多进程应用于自动化测试

    多线程与多进程与自动化测试用例结合起来执行,从而节省测试用例的总体运行时间. 多线程执行测试测试用例 以百度搜索为例,通过不同的浏览器来启动不同的线程. from selenium import we ...

  6. Python之多线程与多进程(二)

    多进程 上一章:Python多线程与多进程(一) 由于GIL的存在,Python的多线程并没有实现真正的并行.因此,一些问题使用threading模块并不能解决 不过Python为并行提供了一个替代方 ...

  7. Python之多线程与多进程(一)

    多线程 多线程是程序在同样的上下文中同时运行多条线程的能力.这些线程共享同一个进程的资源,可以在并发模式(单核处理器)或并行模式(多核处理器)下执行多个任务 多线程有以下几个优点: 持续响应:在单线程 ...

  8. Python中多线程与多进程的恩恩怨怨

    概念: 并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运 ...

  9. python的多线程、多进程、协程用代码详解

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:刘早起早起 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...

随机推荐

  1. DeleteFolder

    import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; /*** * @author ...

  2. java-整型数值 用 16进制转换、2进制转换-Integer.toHexString

    负数为什么要用补码表示 可以将符号位和其它位统一处理 减法也可按加法来处理 另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃 正数:原码.反码.补码相同. 负数:反码符号位不变 ...

  3. Spring 中使用 @Scheduled 创建定时任务

    一.定时任务触发条件 1.在 Application 启动类上添加:@EnableScheduling 2.含定时方法的类上添加注解:@Component,该注解将定时任务类纳入 spring bea ...

  4. UE制作PBR材质攻略Part 1 - 色彩知识

    目录 一.前言 二.色彩知识 2.1 色彩理论 2.1.1 成像原理 2.1.2 色彩模型和色彩空间 2.1.3 色彩属性 2.1.4 直方图 2.1.5 色调曲线 2.1.6 线性空间与Gamma空 ...

  5. 【CV现状-3.3】特征提取与描述

    #磨染的初心--计算机视觉的现状 [这一系列文章是关于计算机视觉的反思,希望能引起一些人的共鸣.可以随意传播,随意喷.所涉及的内容过多,将按如下内容划分章节.已经完成的会逐渐加上链接.] 缘起 三维感 ...

  6. 微人事 star 数超 10k,如何打造一个 star 数超 10k 的开源项目

    看了下,微人事(https://github.com/lenve/vhr)项目 star 数超 10k 啦,松哥第一个 star 数过万的开源项目就这样诞生了. 两年前差不多就是现在这个时候,松哥所在 ...

  7. jsonp 跨域Uncaught SyntaxError: Unexpected token :解决方法

    [jQuery]Ajax实现跨域访问JSON Ajax跨域访问JSON 环境:.net4.0+jQuery+JSON.net 因为在跨域实现,所以这里新建网站,这个网站只需要Ashx文件 public ...

  8. python 基本排序算法

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/9/15 下午5:06 # @Author : lb # @File : so ...

  9. Flutter 修改SDK 路径出现的问题

    更换Flutter SDK 路径之后出现的问题. 解决方法:flutter packages upgrade. 替换: 替换这两个文件里的路径. 同时修改电脑配置文件里的路径: open -a Tex ...

  10. Java 8 Optional:优雅地避免 NPE

    本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查.在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的. 空指针异常(NullPo ...