python多线程菜鸟教程,python 多线程有几种实现方法

  python多线程菜鸟教程,python 多线程有几种实现方法

  本文主要介绍Python中的多线程。线程是进程中正在执行的程序的执行路径,一个程序至少有一个执行路径。这篇文章给你做了非常详细的介绍,有需要的朋友可以参考一下。

  00-1010什么是多线程:线程模块守护进程线程锁用join方法线程(互斥)GIL VS LockRLock(递归锁)信号量(信号量)事件(事件)生产者-消费者模型线程池

  

目录

  流程:运行程序,QQ 360.

  线程:指进程中正在执行的程序的执行路径,一个程序至少有一个执行路径。(360中的杀毒电脑如果同时运行电脑清理需要打开多个路径)

  每个线程都有自己要运行的内容,这些内容可以称为线程要执行的任务。

  开启多线程是为了同时运行代码的多个部分。

  好处:解决了多个部分需要同时运行的问题。

  缺点:线程过多会导致效率低下(因为程序的执行是由随机快速切换的CPU来完成的)

  线程和进程的区别线程共享内存,进程有独立的内存。线程启动速度块,所以进程启动速度慢,运行速度没有可比性。同一个进程的线程可以直接通信,两个进程想要通信,那么通过中间代理创建一个新线程就非常简单了。创建新流程需要克隆其父流程一次。一个线程可以控制和操作同一线程中的其他线程,但一个进程只能操作其子进程。

  

什么是多线程:

  多线程的使用:直接使用

  # -*-编码:utf-8 -*-

  #使用螺纹的第一种方式

  导入线程

  导入时间

  #需要多线程的函数

  def fun(参数):

  Print(我是线程%s% args

  时间.睡眠(2)

  Print(线程%s已运行完 % args )

  #创建线程

  t1=线程。线程(target=fun,args=(1,))

  t2=线程。线程(target=fun,args=(2,))

  start_time=time.time()

  t1.start()

  t2.start()

  end_time=time.time()

  Print(两个线程的总运行时间为:,end_time-start_time)

  打印(“主线程结束”)

  运行结果:

  我是线程1。

  我是线程2。这两个线程的总运行时间是:0.500000000001

  主线程结束

  线程1运行完毕。

  线程运行结束。

  线程的第二个用途:继承调用

  #继承的呼叫

  导入线程

  导入时间

  类MyThreading(线程。螺纹):

  def __init__(self,name):

  超级(MyThreading,self)。__init__()

  self.name=name

  #线程要运行的代码

  定义运行(自身):

  打印(我是线程%s% self.name )

  时间.睡眠(2)

  Print(线程%s已运行完 % self.name )

  t1=MyThreading(1)

  t2=流言阅读(2)

  start_time=time.time()

  t1.start()

  t2.start()

  end_time=time.time()

  Print(两个线程的总运行时间为:,end_time-start_time)

  打印(“主线程结束”)

  运行结果:

  我是线程1。

  我是线程2。

  这两个线程的总运行时间是:0.500000000001

  主线程结束

  线程运行结束。

  线程1运行完毕。

  

threading模块

  在Python多线程编程中,join方法的活动线程是同步的。守护进程的存在是为了保护其他人。当设置为守护进程时,守护的主线程不存在后守护进程自然也不存在。第一个:python多线程。

  认情况

  • Python多线程默认情况(设置线程setDaemon(False)),主线程执行完自己的任务后,就退出了,此时子线程会继续执行自己的任务,直到子线程任务结束
  • 代码演示:threading中的两个创建多线成的例子都是。
  • 第二种:开启守护线程
  • 开启线程的setDaemon(True)),设置子线程为守护线程,实现主程序结束,子程序立马全部结束功能
  • 代码演示:
  •   

    # 守护线程

      import threading

      import time

      class MyThreading(threading.Thread):

       def __init__(self, name):

       super(MyThreading, self).__init__()

       self.name = name

       # 线程要运行的代码

       def run(self):

       print("我是线程%s" % self.name)

       time.sleep(2)

       print("线程%s运行结束" % self.name)

      t1 = MyThreading(1)

      t2 = MyThreading(2)

      start_time = time.time()

      t1.setDaemon(True)

      t1.start()

      t2.setDaemon(True)

      t2.start()

      end_time = time.time()

      print("两个线程一共的运行时间为:", end_time-start_time)

      print("主线程结束")

      

    • 注意:如果要设置为守护线程,一定要在开启线程之前,将该线程设置为守护线程
    • 结论:主线程结束后,无论子线程1,2是否运行完成,都结束线程,不在继续向下运行
    • 第三种:加入join方法设置同步
    • 当不给程序设置守护进程时,主程序将一直等待子程序全部运行完成才结束
    • 代码演示:

      

    # join:线程同步

      import threading

      import time

      class MyThreading(threading.Thread):

       def __init__(self, name):

       super(MyThreading, self).__init__()

       self.name = name

       # 线程要运行的代码

       def run(self):

       print("我是线程%s" % self.name)

       time.sleep(3)

       print("线程%s运行结束" % self.name)

      threading_list = []

      start_time = time.time()

      for x in range(50):

       t = MyThreading(x)

       t.start()

       threading_list.append(t)

      for x in threading_list:

       x.join() # 为线程开启同步

      end_time = time.time()

      print("50个线程一共的运行时间为:", end_time-start_time)

      print("主线程结束")

      结论:主线程等待50个子线程全部执行完成才结束。

      

      

    线程锁(互斥锁Mutex)

      

    • 一个进程下可以启用多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时如果多个线程同时要修改一份数据,会出现什么状况?
    • 代码演示:

      

    # -*- coding:utf8 -*-

      import threading

      import time

      num = 100

      threading_list = []

      def fun():

       global num

       print("get num:", num)

       num += 1

       time.sleep(1)

      for x in range(200):

       t = threading.Thread(target=fun)

       t.start()

       threading_list.append(t)

      for x in threading_list:

       x.join()

      print("nun:", num)

      

    • 结论:运行结果可能会出现num<300的情况
    • 正常来讲,这个num结果应该是300, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是300,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行加1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是101,但此时B线程运算完的结果也是101,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是101。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。

      *注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

      加锁版本:

      

    import random

      import threading

      import time

      num = 100

      threading_list = []

      def fun():

       global num

       time.sleep(random.random())

       lock.acquire() # 加锁

       print("get num:", num, threading.current_thread())

       num += 1

       lock.release() # 释放锁

      # 实例化锁对象

      lock = threading.Lock()

      for x in range(200):

       t = threading.Thread(target=fun)

       t.start()

       threading_list.append(t)

      for x in threading_list:

       x.join()

      print("num:", num)

      

      

    GIL VS Lock

      机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。

      

      那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。

      

      

    RLock(递归锁)

      说白了就是在一个大锁中还要再包含子锁

      

    import threading, time

      def run1():

       lock.acquire()

       print("grab the first part data")

       global num

       num += 1

       lock.release()

       return num

      def run2():

       lock.acquire()

       print("grab the second part data")

       global num2

       num2 += 1

       lock.release()

       return num2

      def run3():

       lock.acquire()

       res = run1()

       print(--------between run1 and run2-----)

       res2 = run2()

       lock.release()

       print(res, res2)

      if __name__ == __main__:

       num, num2 = 0, 0

       lock = threading.RLock()

       for i in range(3):

       t = threading.Thread(target=run3)

       t.start()

      while threading.active_count() != 1:

       print(threading.active_count())

      else:

       print(----all threads done---)

       print(num, num2)

      在开发的过程中要注意有些操作默认都是 线程安全的(内部集成了锁的机制),我们在使用的时无需再通过锁再处理,例如:

      

    import threading

      data_list = []

      lock_object = threading.RLock()

      def task():

       print("开始")

       for i in range(1000000):

       data_list.append(i)

       print(len(data_list))

      for i in range(2):

       t = threading.Thread(target=task)

       t.start()

      

      

      

    Semaphore(信号量)

      

    • 互斥锁同时只允许一个线程修改数据,而Semaphore是同时允许一定数量的线程修改数据,比如厕所有三个坑,那最多只允许三个人上厕所,后面的人只能等前面的人出来才能进去。
    • 代码演示:

      

    # -*- coding:GBK -*-

      import threading

      import time

      sum_1 = 0

      def run(i):

       global sum_1

       time.sleep(1)

       # lock.acquire()

       semaphore.acquire()

       sum_1 += 1

       print("线程%s来了,并修改了sum_1的值为:%s" % (i, sum_1))

       semaphore.release()

       # lock.release()

      # lock = threading.Lock()

      semaphore = threading.BoundedSemaphore(5)

      for x in range(10):

       t = threading.Thread(target=run, args=(x,))

       t.start()

      while threading.active_count() != 1:

       pass

      print("程序结束")

      

      

    Event(事件)

      

    • 通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
    • 四个常用方法

      set() # 设置标志位为 True
    clear() # 清空标志位(将标志位改为false)
    is_set() # 检测标志位,如果标志位被设置,返回True,否则返回False
    wait() # 等待标志位被设置位True程序才继续往下运行

      代码演示:

      

    # -*- coding:utf-8 -*-

      import threading

      import time

      def light():

       count = 1

       event.set() # 设置标志位 True

       while True:

       if count <= 10:

       print("现在是绿灯")

       time.sleep(1)

       elif count <= 15:

       print("现在是红灯")

       event.clear() # 清空标志位(将标志位改为false)

       time.sleep(1)

       else:

       count = 0

       event.set()

       count += 1

      def car(name):

       while True:

       if event.is_set():

       print("----------%s在起飞-------------" % name)

       time.sleep(1)

       else:

       print("---------%s在等红灯---------------" % name)

       event.wait() # 等待标志位被设置位True程序才继续往下运行

      event = threading.Event()

      light_1 = threading.Thread(target=light)

      light_1.start()

      for x in range(5):

       car_1 = threading.Thread(target=car, args=("马自达"+str(x),))

       car_1.start()

      红绿灯案例

      Queue(队列)

      

    queue.Queue(maxsize=0)

      #队列:先进先出 maxsize:设置队列的大小

      queue.LifoQueue(maxsize=0)

      ##last in fisrt out maxsize:设置队列的大小

      queue.PriorityQueue(maxsize=0)

      #存储数据时可设置优先级的队列,按优先级顺序(最低的先) maxsize:设置队列的大小

      exceptionqueue.Empty

      Exception raised when non-blocking get()(or get_nowait()) is called on a Queue object which is empty.

      当在一个空的队列对象上调用非阻塞的get()(或get_nowait())时,会产生异常。

      exceptionqueue.Full

      Exception raised when non-blocking put() (or put_nowait() ) is called on a Queue object which is full.

      当非阻塞的put()(或put_nowait())被调用到一个已满的队列对象上时引发的异常。

      

    import queue

      # 实例化队列对象

      q = queue.Queue(3)

      print(q.qsize()) # 获取队列内数据的长度

      print(q.empty()) # 如果队列是空的,返回True,否则返回False(不可靠!)

      print(q.full()) # 如果队列已满,返回True,否则返回False(不可靠!)。

      """

      Queue.put(item, block=True, timeout=None)

      可以简写:Queue.put(item, True, 5)

      将项目放入队列。

      如果可选的args block为true(默认值),并且timeout为None(默认值),必要时进行阻塞,直到有空闲的槽。

      如果timeout是一个正数,它最多阻断timeout秒,如果在这段时间内没有空闲槽,则引发Full异常。

      否则(block为false),如果有空闲的槽立即可用,就在队列上放一个项目,否则就引发Full异常(在这种情况下忽略超时)。

      """

      q.put(1) # 数据“1”进入队列

      q.put("nihao") # 数据"nihao"进入队列

      q.put("456ni", block=True, timeout=5)

      将一个项目放入队列中,不进行阻断。

      只有在有空闲位置的情况下才排队。

      否则会引发Full异常。

      # q.put_nowait(123)

      Queue.get(block=True, timeout=None)

      可以简写:Queue.get(True, 3)

      从队列中删除并返回一个项目。

      如果可选的argsblock为True(默认),timeout为无(默认)。

       就会在必要时阻塞,直到有一个项目可用。

       如果timeout是非负数,它最多阻断timeout秒,如果在这段时间内没有项目可用,则引发Empty异常。

      否则(block为False),如果有一个项目立即可用,则返回一个项目。

       否则引发Empty异常(timeout被忽略了在这种情况下)。

      print(q.get())

      print(q.get())

      print(q.get())

      print(q.get(block=True, timeout=2))

      从队列中移除并返回一个项目,而不阻塞。

      只有当一个项目立即可用时,才会得到一个项目。

      否则引发Empty异常。

      # print(q.get_nowait())

      

      

    生产者消费者模型

      在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

      为什么要使用生产者和消费者模式

      在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

      什么是生产者消费者模式

      生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

      

    # 生产者/消费者

      import threading

      import queue

      import time

      # 生产者

      def producer(name):

       count = 1

       while True:

       p.put("{}骨头{}".format(name, count))

       print("骨头{}被{}生产".format(count, name).center(60, "*"))

       count += 1

       time.sleep(0.1)

      # 消费者

      def consumer(name):

       while True:

       print("{}被{}吃掉了".format(p.get(), name))

      # 实例化队列对象

      p = queue.Queue(10)

      # 创建生产者线程

      producer_threading1 = threading.Thread(target=producer, args=("飞某人",))

      producer_threading2 = threading.Thread(target=producer, args=("Alex",))

      # 创建消费者线程

      consumer_threading1 = threading.Thread(target=consumer, args=("张三",))

      consumer_threading2 = threading.Thread(target=consumer, args=("李四",))

      producer_threading1.start()

      producer_threading2.start()

      consumer_threading1.start()

      consumer_threading2.start()

      

      

    线程池

      Python3中官方才正式提供线程池。

      线程不是开的越多越好,开的多了可能会导致系统的性能更低了,例如:如下的代码是不推荐在项目开发中编写。

      

    import threading

      def task(video_url):

       pass

      url_list = ["www.xxxx-{}.com".format(i) for i in range(30000)]

      for url in url_list:

       t = threading.Thread(target=task, args=(url,))

       t.start()

      # 这种每次都创建一个线程去操作,创建任务的太多,线程就会特别多,可能效率反倒降低了。

      建议:使用线程池

      

    import time

      from concurrent.futures import ThreadPoolExecutor # 并行期货,线程池执行者

      """

      pool = ThreadPoolExecutor(100)

      pool.submit(函数名,参数1,参数2,参数...)

      """

      def task(video_url, num):

       print("开始执行任务", video_url, num) # 开始执行任务 www.xxxx-299.com 3

       time.sleep(1)

      # 创建线程池,最多维护10个线程

      threadpool = ThreadPoolExecutor(10)

      # 生成300网址,并放入列表

      url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]

      for url in url_list:

       """

       在线程池中提交一个任务,线程池如果有空闲线程,则分配一个线程去执行,执行完毕后在将线程交还给线程池,

       如果没有空闲线程,则等待。注意在等待时,与主线程无关,主线程依然在继续执行。

       """

       threadpool.submit(task, url, 3)

      print("等待线程池中的任务执行完毕中······")

      threadpool.shutdown(True) # 等待线程池中的任务执行完毕后,在继续执行

      print("END")

      

      

      任务执行完任务,再干点其他事:

      

    """线程池的回调"""

      import time

      import random

      from concurrent.futures import ThreadPoolExecutor

      def task(video_url):

       print("开始执行任务", video_url)

       time.sleep(1)

       return random.randint(0, 10) # 将结果封装成一个Futuer对象,返回给线程池

      def done(response): # response就是futuer对象,也就是task的返回值分装的一个Futuer对象

       print("任务执行完后,回调的函数", response.result()) # 即Futuer.result():取出task的返回值

      # 创建线程池

      threadpool = ThreadPoolExecutor(10)

      url_list = ["www.xxxx-{}.com".format(i) for i in range(5)]

      for url in url_list:

       futuer = threadpool.submit(task, url) # futuer是由task返回的一个Future对象,里面有记录task的返回值

       futuer.add_done_callback(done) # 回调done函数,执行者依然是子线程

      # 优点:可以做分工,例如:task专门下载,done专门将下载的数据写入本地文件。

      到此这篇关于详解Python中的多线程的文章就介绍到这了,更多相关Python多线程内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

    郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

    留言与评论(共有 条评论)
       
    验证码: