https://docs.python.org/3.7/library/concurrency.html
python程序默认是单线程的,也就是说在前一句语句执行完之前后面的语句不能继续执行
先感受一下线程,一般情况下:

def testa():
sleep(1)
print "a" def testb():
sleep(1)
print "b" testa()
testb()
#先隔出一秒打印出a,再过一秒打出b

但是如果用了threading的话:

ta = threading.Thread(target=testa)
tb = threading.Thread(target=testb)
for t in [ta,tb]:
t.start()
for t in [ta,tb]:
t.join()
print "DONE" #输出是ab或者ba(紧贴着的)然后空一行再来DONE的结果。

得到这样的结果是因为这样的,在start之后,ta首先开始跑,但是主线程(脚本本身)没有等其完成就继续开始下一轮循环,然后tb也开始了,在之后的一段时间里,ta和tb两条线程(分别代表了testa和testb这两个过程)共同执行。相对于一个个迭代而言,这样做无疑是大大提高了运行的速度。

  Thread类为线程的抽象类,其构造方法的参数target指向一个函数对象,即该线程的具体操作。此外还可以有args=<tuple>来给target函数传参数。需要注意的是当传任何一个序列进去的话Thread会自动把它分解成单个单个的元素然后分解传给target函数。我估计在定义的时候肯定是*args了。

  join方法是个很tricky的东西,至今还不是很清楚地懂这是个什么玩意儿。join([timeout])方法阻塞了主线程,直到调用此方法的子线程完成之后主线程才继续往下运行。(之前我糊里糊涂地把join就紧紧接在start后面写了,如果这么写了的话那么多线程在速度上就毫无优势,和单线程一样了= =)。而像上面这个示例一样,先一个遍历把所有线程 都启动起来,再用一个遍历把所有线程都join一遍似乎是比较通行的做法。

关于线程锁

  多线程程序涉及到一个问题,那就是当不同线程要对同一个资源进行修改或利用时会出现混乱,所以有必要引入线程锁。

  可以通过Thread.Lock类来创建简单的线程锁。lock = threading.Lock()即可。在某线程start之前,让lock.acquire(),且lock在acquire()之后不能再acquire,否则会报错。当线程结束后调用lock.release()来释放锁就好了。一般而言,有锁的多线程场景可以提升一部分效率,但在写文件等时机下会有阻塞等待的情况。相比之下,无所多线程场景可以进一步提升效率,但是可能会引起读写冲突等问题,所以要慎用。一定要确认各个线程间没有共同的资源之类的问题后再实行无锁多线程。

  ●  以上的包装线程的方式是一种面向过程的方法,下面介绍一下如何面向对象地来抽象线程

  面向对象地抽象线程需要自定义一个类继承Thread类。比如自定义class MyThread(Thread)。这个类的一个实例就是代表了一个线程,然后通过重载这个类中的run方法(是run,不是start!!但start的动作确实就是调用run)来执行具体的操作。此时锁可以作为一个构造方法的参数,将一个锁传进不同的实例中以实现线程锁控制。比如:

#方法二:从Thread继承,并重写run()
class MyThread(threading.Thread):
def __init__(self,arg):
super(MyThread, self).__init__()#注意:一定要显式的调用父类的初始化函数。
self.arg=arg
def run(self):#定义每个线程要运行的函数
time.sleep(1)
print 'the arg is:%s\r' % self.arg for i in xrange(4):
t =MyThread(i)
t.start() print 'main thread end!'

Thread类还有以下的一些方法,自定义的类也可以调用

    getName()

    setName(...)  //其实Thread类在构造方法中有一个name参数,可以为相应的线程取一个名字。这两个方法就是相关这个名字属性的

    isAlive()  一个线程从start()开始到run()结束的过程中没有异常,则其实alive的。

    setDaemon(True/False)  是否设置一个线程为守护线程。当你设置一个线程为守护线程之后,程序不会等待这个线程结束再退出程序,可参考http://blog.csdn.net/u012063703/article/details/51601579

  ●  除了Thread类,threading中还有以下一些属性,简单介绍一下:

    Timer类,Timer(int,target=func)  和Thread类类似,只不过它在int秒过后才以target指定的函数开始线程运行

    currentThread()  获得当前线程对象

    activeCount()  获得当前活动的线程总个数

    enumerate()  获得所有活动线程的列表

    settrace(func)  设置一跟踪函数,在run执行前执行

    setprofile(func)  设置一跟踪函数,在run执行完毕之后执行

Queue用于建立和操作队列,常和threading类一起用来建立一个简单的线程队列。

  首先,队列有很多种,根据进出顺序来分类,可以分成

    Queue.Queue(maxsize)  FIFO(先进先出队列)

    Queue.LifoQueue(maxsize)  LIFO(先进后出队列)

    Queue.PriorityQueue(maxsize)  为优先度越低的越先出来

    如果设置的maxsize小于1,则表示队列的长度无限长

  FIFO是常用的队列,其一些常用的方法有:

    Queue.qsize()  返回队列大小

    Queue.empty()  判断队列是否为空

    Queue.full()  判断队列是否满了

    Queue.get([block[,timeout]])  从队列头删除并返回一个item,block默认为True,表示当队列为空却去get的时候会阻塞线程,等待直到有有item出现为止来get出这个item。如果是False的话表明当队列为空你却去get的时候,会引发异常。在block为True的情况下可以再设置timeout参数。表示当队列为空,get阻塞timeout指定的秒数之后还没有get到的话就引发Full异常。

    Queue.put(...[,block[,timeout]])  向队尾插入一个item,同样若block=True的话队列满时就阻塞等待有空位出来再put,block=False时引发异常。同get的timeout,put的timeout是在block为True的时候进行超时设置的参数。

    Queue.task_done()  从场景上来说,处理完一个get出来的item之后,调用task_done将向队列发出一个信号,表示本任务已经完成

    Queue.join()  监视所有item并阻塞主线程,直到所有item都调用了task_done之后主线程才继续向下执行。这么做的好处在于,假如一个线程开始处理最后一个任务,它从任务队列中拿走最后一个任务,此时任务队列就空了但最后那个线程还没处理完。当调用了join之后,主线程就不会因为队列空了而擅自结束,而是等待最后那个线程处理完成了。

  结合threading和Queue可以构建出一个简单的生产者-消费者模型,比如:

import threading
import Queue
import time
class worker(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
self.queue=queue
self.thread_stop=False def run(self):
while not self.thread_stop:
print("thread%d %s: waiting for tast" %(self.ident,self.name))
try:
task=q.get(block=True, timeout=20)#接收消息
except Queue.Empty:
print("Nothing to do!i will go home!")
self.thread_stop=True
break
print("task recv:%s ,task No:%d" % (task[0],task[1]))
print("i am working")
time.sleep(3)
print("work finished!")
q.task_done()#完成一个任务
res=q.qsize()#判断消息队列大小
if res>0:
print("fuck!There are still %d tasks to do" % (res)) def stop(self):
self.thread_stop = True if __name__ == "__main__":
q=Queue.Queue(3)
worker=worker(q)
worker.start()
q.put(["produce one cup!",1], block=True, timeout=None)#产生任务消息
q.put(["produce one desk!",2], block=True, timeout=None)
q.put(["produce one apple!",3], block=True, timeout=None)
q.put(["produce one banana!",4], block=True, timeout=None)
q.put(["produce one bag!",5], block=True, timeout=None)
print("***************leader:wait for finish!")
q.join()#等待所有任务完成
print("***************leader:all task finished!")

输出是这样的

thread139958685849344 Thread-1: waiting for tast 1
task recv:produce one cup! ,task No:1
i am working
work finished!
fuck!There are still 3 tasks to do
thread139958685849344 Thread-1: waiting for tast 1
task recv:produce one desk! ,task No:2
i am workingleader:wait for finish!
work finished!
fuck!There are still 3 tasks to do
thread139958685849344 Thread-1: waiting for tast 1
task recv:produce one apple! ,task No:3
i am working
work finished!
fuck!There are still 2 tasks to do
thread139958685849344 Thread-1: waiting for tast 1
task recv:produce one banana! ,task No:4
i am working
work finished!
fuck!There are still 1 tasks to do
thread139958685849344 Thread-1: waiting for tast 1
task recv:produce one bag! ,task No:5
i am working
work finished!
thread139958685849344 Thread-1: waiting for tast 1
***************leader:all task finished!
Nothing to do!i will go home!

上例中并没有性能的提升(毕竟还是只有一个线程在跑)。线程队列的意义并不是进一步提高运行效率,而是使线程的并发更加有组织。可以看到,在增加了线程队列之后,程序对于线程的并发数量就有了控制。新线程想要加入队列开始执行,必须等一个既存的线程完成之后才可以。举个例子,比如

for i in range(x):
t = MyThread(queue)
t.start()

x在这里是个变量,我们不知道这个循环会触发多少线程并发,如果多的话就会很冒险。但是有了队列之后,把一个队列作为所有线程构建线程对象时的一个参数,让线程必须按照这个队列规定的大小来执行的话,就不担心过多线程带来的危险了。

python 多线程并发threading & 任务队列Queue的更多相关文章

  1. 【Python】 多线程并发threading & 任务队列Queue

    threading python程序默认是单线程的,也就是说在前一句语句执行完之前后面的语句不能继续执行(不知道我理解得对不对) 先感受一下线程,一般情况下: def testa(): sleep(1 ...

  2. 用Queue控制python多线程并发数量

    python多线程如果不进行并发数量控制,在启动线程数量多到一定程度后,会造成线程无法启动的错误. 下面介绍用Queue控制多线程并发数量的方法(python3). # -*- coding: utf ...

  3. Python多线程(threading模块)

    线程(thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. ...

  4. Python 多线程并发程序设计与分析

    多线程并发程序设计与分析 by:授客 QQ:1033553122 1.技术难点分析与总结 难点1:线程运行时,运行顺序不固定 难点2:同一段代码,再不加锁的情况下,可能被多个线程同时执行,这会造成很多 ...

  5. Python多线程笔记(三),queue模块

    尽管在Python中可以使用各种锁和同步原语的组合编写非常传统的多线程程序,但有一种首推的编程方式要优于其他所有编程方式即将多线程程序组织为多个独立人物的集合,这些任务之间通过消息队列进行通信 que ...

  6. Python多线程并发的误区

    由于项目要做一个并发测试,由于断言的东西较多,决定手写脚本.于是用python写了脚本: def test_method(thread_no): print("%s===test_metho ...

  7. Python多线程(3)——Queue模块

    Queue模块支持先进先出(FIFO)队列,支持多线程的访问,包括一个主要的类型(Queue)和两个异常类(exception classes). Python 2 中的Queue模块在Python ...

  8. python多线程与threading模块

    python多线程与_thread模块 中介绍了线程的基本概念以及_thread模块的简单示例.然而,_thread模块过于简单,使得我们无法用它来准确地控制线程,本文介绍threading模块,它提 ...

  9. python多线程--优先级队列(Queue)

    Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.这些队列都实现 ...

随机推荐

  1. AOP切面通知

    需要用的基本的jar包: aopalliance-1.0.jaraspectj-1.6.8.jaraspectjweaver-1.6.8.jarcommons-logging-1.1.3.jarspr ...

  2. 机器学习基础算法__python实现(基于numpy等基础库)

    # 博客转自https://blog.csdn.net/weixin_39561100/article/details/80879211 主要是将<机器学习实战>中的算法实现一遍,后续会慢 ...

  3. [POI2009]SLO

    Description 对于一个1-N的排列(ai),每次你可以交换两个数ax与ay(x<>y),代价为W(ax)+W(ay) 若干次交换的代价为每次交换的代价之和.请问将(ai)变为(b ...

  4. Java中的流(1)流简介

    简介 1.在java中stream代表一种数据流(源),java.io的底层数据元.(比作成水管)2.InputStream 比作进水管,水从里面流向你,你要接收,read3.OutputStream ...

  5. XmlPullParser接口详述

    带*的是非常重要的函数.点击有说明.setInputgetDepthisWhitespacegetTextisEmptyElementTaggetAttributeCountgetAttributeV ...

  6. ORA-00445: Background Process "xxxx" Did Not Start After 120 Seconds

    Recent linux kernels have a feature called Address Space Layout Randomization (ASLR).ASLR  is a feat ...

  7. JSP(Java Servlet Page)

    一.简介 HTML HTML擅长显示一个静态的网页,但是不能调用Java程序. Servlet Servlet擅长调用Java程序和后台进行交互,但是它不擅长显示一个完整的HTML页面. 我们希望创建 ...

  8. 【CSS】3种CSS方法设置元素垂直水平居中

    1. 宽高固定 设置要水平垂直居中的 div 的 position 为 absolute,left:50%; margin-left为负的这个元素宽度的一半,同理,top:50%;margin-top ...

  9. C#运用存储过程新增一条记录并返回自动生成的ID

    @Hcy黄灿奕:http://blog.sina.com.cn/iihcy 前言: 1.存储过的好处: 存储过程相对于其他的数据库访问方法有以下的优点: (1)重复使用.存储过程可以重复使用,从而可以 ...

  10. C++学习_继承覆盖重载

    今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别. 首先来明确一个概念,函数名即地址,也就是说函数名就是个指针. 编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码 ...