必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

本节内容,必威:Python的Queue模块中提供了同步的

2019-11-03 14:24 来源:未知

Python queue队列

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产⽣数据的模块,就形象地称为生产者;⽽而处理数据的模块,就称为消费者。

本节内容

作用:

单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

  1. 进程与线程的概念
  2. Python threading 模块
  3. GIL——global interpreter lock
  4. Mutex互斥锁(线程锁)
  5. Semaphore信号量
  6. Events事件
  7. Queue队列

   解耦:使程序直接实现松耦合,修改一个函数,不会有串联关系。

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先⼊先出)队列Queue,LIFO(后⼊先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么就做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。

 

   提高处理效率:FIFO = 现进先出,LIFO = 后入先出。

⽣产者消费者模式的说明:

1、进程与线程的概念

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。正是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

 

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

有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。但进程还是有很多缺陷的,主要体现为:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

队列:

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

什么是进程(process)?

对各种资源管理的集合,称之为进程。各类资源包括:内存的调用、网卡的调用等等。

每一个应用程序就是一个进程。

一个进程至少包含一个线程。

正在运行的进程都有唯一的一个ID号,即PID。如下图:

必威 1

操作系统调用进程的时候,不会用进程名称进行调用,而是用唯一的PID进行调用。

  队列可以并发的派多个线程,对排列的线程处理,并切每个需要处理线程只需要将请求的数据放入队列容器的内存中,线程不需要等待,当排列完毕处理完数据后,线程在准时来取数据即可。请求数据的线程只与这个队列容器存在关系,处理数据的线程down掉不会影响到请求数据的线程,队列会派给其他线程处理这分数据,它实现了解耦,提高效率。队列内会有一个有顺序的容器,列表与这个容器是有区别的,列表中数据虽然是排列的,但数据被取走后还会保留,而队列中这个容器的数据被取后将不会保留。当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用。

这个阻塞队列就是⽤来给⽣产者和消费者解耦的。纵观⼤多数设计模式,都会找⼀个第三者出来进⾏解耦

什么是线程(thread)?

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

所以线程可以简单的理解为:  线程 = 一堆指令

【注意】:进程要操作CPU,必须先创建一个线程。

总结:进程就是各种资源的集合,线程是一堆指令。进程包含一个或多个线程,进程的操作是要靠线程进行的。

 

进程、线程与内存的关系:

  进程的内存空间相对独立,进程之间不能互相访问对方的内存空间。

  所有在同一个进程里的线程是共享同一块内存空间的。

 

 

进程与线程的区别?

Q1:进程快还是线程快?

A:这没有可比性,进程是资源的集合,线程是一堆指令,进程想要执行也是依靠线程进行的。

Q2:启动一个进程快还是启动一个线程快?

A:肯定是启动一个线程快了。启动进程需要向OS申请各种资源,但是启动线程就是生成一堆指令,一下子就出来了。

区别:

  1、线程共享内存空间,进程的内存是相互独立的。

  2、同一个父进程创建的子进程之间,内存空间相互独立。但同一个父进程创建的子线程之间,内存空间共享。

  3、同一个进程的线程之间可以直接交流;两个进程之间想通信,必须通过一个中间代理。

  4、创建新线程很简单,创建新进程需要对其父进程进行克隆

  5、一个线程可以控制和操作同一个进程里的其他线程,但进程只能操作子进程。

  6、对主线程的修改,可能会影响同一个进程里的其他线程的行为。但是对父进程的修改不会影响其他子进程。

 

Python四种类型的队例:

什么时候使用多线程:

  I/O操作不占CPU

  计算占用CPU

python多线程不适合CPU密集操作型的任务。适合I/O操作密集型的任务。

因为python多线程是伪多线程,其实是在不同的线程之间进行切换,切换就要保持当前线程状态,读取下一线程状态,这些操作会增加CPU负载。所以对于本身就是CPU密集型的任务而言并不适合多线程。

 

Queue:FIFO 即first in first out 先进先出

LifoQueue:LIFO 即last in first out 后进先出

PriorityQueue:优先队列,级别越低,越优先
deque:双边队列


导入三种队列,包

from queue import Queue,LifoQueue,PriorityQueue

2、Python threading模块

线程有2种调用方式,如下:

直接调用(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import threading
import time
 
def sayhi(num): #定义每个线程要运行的函数
 
    print("running on number:%s" %num)
 
    time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
 
    t1.start() #启动线程
    t2.start() #启动另一个线程
 
    print(t1.getName()) #获取线程名
    print(t2.getName())

继承式调用(不推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
import time
 
 
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
 
    def run(self):#定义每个线程要运行的函数
 
        print("running on number:%s" %self.num)
 
        time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

这里会出现一个问题:

在用time模块统计一个主线程运行时间时,由于多线程独立运行,所以这里的计时器只能统计主线程运行时间,这个时间不包括子线程运行时间,即不管子线程是否执行完毕,只要主线程执行完毕,计时器就得出结果。

在使用time模块计时时需要注意这个坑。。。。。。

如果非要在主线程中计算所有线程执行的时间的话,可以先让主线程等待子线程的执行结果,然后再计算时间。这里用到的方法是.join()

例如,我想等待t1这个子线程的执行结果,就在主线程中使用指令:  t1.join()

 

如果用循环的方式启动子线程,会出现一个问题,没办法对子线程进行命名,或者说没办法给子线程门牌号。这样不利于后段程序的调用。

这时只需要在实例化子线程时使用一个临时变量,然后通过.append()方法,把每个子线程对应的对象加入之前设置好的列表中即可。

然后用for循环遍历存放子线程的列表,即可按顺序取出之前新建的子线程对象。

 

主线程就是程序本身,自己是看不到的。但是可以通过一个命令来证明,.py文件就是主线程。

  print(threading.current_thread())   #打印当前线程

如果结果中出现 MainThread就是表示这个线程是主线程。没有则表示是子线程。

 

TAG标签:
版权声明:本文由必威发布于必威-编程,转载请注明出处:本节内容,必威:Python的Queue模块中提供了同步的