Python Threading 多线程

Python线程

线程是操作系统中能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个CPU执行时所需要的一串指令。

假设我们正在阅读一本书,没有读完,想出去休息一下,但是想在回来时恢复到此前读的具体进度。有一个方法就是记下页数、行数,这些数值就是execution context(执行上下文)。如果我们的舍友在我们休息的时候,使用相同的方法读这本书。大家只需要将页数、行数记下来就可以在交替的时间共同阅读这本书了。

线程的工作方式与此类似,一个CPU会给你一个在同一时间能够做多个运算的表象,实际上它在每个运算上只用了极少的时间,本质上同一时刻CPU只做了一件事。它能这样做就是因为它有每个运算的execution context,就像大家共享同一本书一样,多任务也能共享同一个CPU。


Python Threading

Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别、原始的线程以及一个简单的锁。

  • threading.currentThread():返回当前的线程变量。
  • threading.enumerate():返回一个包含正在运行的线程的列表,不包括启动前和终止后的线程。
  • threading.activeCount():返回正在运行的线程数量。
  • 除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run():用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]):阻塞线程调用,直到线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive() :返回线程是否在运行中。
  • getName(): 获取线程名称。
  • setName(): 设置线程名称。
  • setDaemon():设置线程为守护线程,必须在启动线程之前进行设置。
  • isDaemon():)判断线程是否是守护线程
  • Python创建多线程主要有如下两种方法:

  • 函数

  • 使用Threading模块通过函数创建线程

    threading.Thread() 一般接收两个参数:

  • 线程函数名:要放置线程让其后台执行的函数,由我们自已定义,注意不要加();
  • 线程函数的参数:线程函数名所需的参数,以元组的形式传入。若不需要参数,可以不指定。

  • 使用Threading模块通过继承创建线程

    使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run(self)方法:

    首先,我们要自定义一个类,对于这个类有两点要求,

  • 必须继承 threading.Thread 这个父类。
  • 必须重写 run 方法。

  • 线程同步

    多线程的优势在于可以同时运行多个任务,多个线程之间内存是共享的,所以线程比进程轻量。多个线程是可以同时访问内存中的数据的,如果多个线程同时修改一个对象,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

    使用Thread对象的Rlock和Lock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

    如下,我们定义了一个共享变量balance,初始值为0,并且启动两个线程,先加后减,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。

    如上就是数据的不同步导致的计算结果与实际预期差异很大,为了避免这种情况,引入了锁的概念。

    如果我们要确保balance计算正确,就要给run_job()上一把锁,当某个线程开始执行run_job()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行run_job(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

    当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

    同时,获得锁的线程用完后一定要释放锁,否则那些等待锁的线程将永远等待下去,成为死线程,因此我们用try…finally…来确保锁一定会被释放。

    锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。


    线程优先级队列( Queue)

    Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

    Queue模块中的常用方法:

  • Queue.qsize():返回队列的大小。
  • Queue.empty(): 如果队列为空,返回True,反之False。
  • Queue.full(): 如果队列满了,返回True,反之False。
  • Queue.get([block[, timeout]]):获取队列,timeout等待时间。
  • Queue.get_nowait(): 相当Queue.get(False)。
  • Queue.put(item) :写入队列,timeout等待时间。
  • Queue.put_nowait(item) :相当Queue.put(item, False)。
  • Queue.task_done() :在完成一项任务之后,Queue.task_done()函数向任务已经完成的队列发送一个信 。
  • Queue.join() :实际上意味着等到队列为空,再执行其他操作。
  • 执行上述代码,输出结果如下

    若对你有所帮助,感谢关注、点赞支持。

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2019年6月9日
    下一篇 2019年6月10日

    相关推荐