• 多进程与多线程


    一 进程与线程的定义:

      进程定义:

      进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

      线程定义:

      线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

    二 进程与线程的关系:

      进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
    线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

    进程和线程的关系:

    (1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
    (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
    (3)CPU分给线程,即真正在CPU上运行的是线程。

    三 并行和并发

    并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行

    并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集

    四 同步与异步

    在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。

    threading模块

    线程对象的创建

    Thread类直接创建

    import threading
    import time
    
    def countNum(n): # 定义某个线程要运行的函数
    
        print("running on number:%s" %n)
    
        time.sleep(3)
    
    if __name__ == '__main__':
    
        t1 = threading.Thread(target=countNum,args=(23,)) #生成一个线程实例
        t2 = threading.Thread(target=countNum,args=(34,))
    
        t1.start() #启动线程
        t2.start()
    
        print("ending!")

    Thread类继承式创建

    #继承Thread式创建
    
    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)
    
    t1=MyThread(56)
    t2=MyThread(78)
    
    t1.start()
    t2.start()
    print("ending")

    Thread类的实例方法

    join()和setDaemon()

    import threading
    from time import ctime,sleep
    import time
    
    def Music(name):
    
            print ("Begin listening to {name}. {time}".format(name=name,time=ctime()))
            sleep(3)
            print("end listening {time}".format(time=ctime()))
    
    def Blog(title):
    
            print ("Begin recording the {title}. {time}".format(title=title,time=ctime()))
            sleep(5)
            print('end recording {time}'.format(time=ctime()))
    
    
    threads = []
    
    
    t1 = threading.Thread(target=Music,args=('FILL ME',))
    t2 = threading.Thread(target=Blog,args=('',))
    
    threads.append(t1)
    threads.append(t2)
    
    if __name__ == '__main__':
    
        #t2.setDaemon(True)
    
        for t in threads:
    
            #t.setDaemon(True) #注意:一定在start之前设置
            t.start()
    
            #t.join()
    
        #t1.join()
        #t2.join()    #  考虑这三种join位置下的结果?
    
        print ("all over %s" %ctime())

    其它方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

     同步锁(lock)

    import time
    import threading
    
    def addNum():
        global num #在每个线程中都获取这个全局变量
        #num-=1
    
        temp=num
        time.sleep(0.1)
        num =temp-1  # 对此公共变量进行-1操作
    
    num = 100  #设定一个共享变量
    
    thread_list = []
    
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)
    
    for t in thread_list: #等待所有线程执行完毕
        t.join()
    
    print('Result: ', num)

    锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

    import threading
    
    R=threading.Lock()
    
    R.acquire()
    '''
    对公共数据的操作
    '''
    R.release()

    死锁与递归锁

    所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待

    的现象,若无外力作用,它们都讲无法推进下去。此时称称系统处于死锁状态或系统产生了死锁,

    这些永远在互相等待的进程成为死锁进程。

    import threading
    import time
    
    mutexA = threading.Lock()
    mutexB = threading.Lock()
    
    class MyThread(threading.Thread):
    
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            self.fun1()
            self.fun2()
    
        def fun1(self):
    
            mutexA.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
    
            print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    
            mutexB.acquire()
            print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
            mutexB.release()
            mutexA.release()
    
    
        def fun2(self):
    
            mutexB.acquire()
            print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
            time.sleep(0.2)
    
            mutexA.acquire()
            print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == "__main__":
    
        print("start---------------------------%s"%time.time())
    
        for i in range(0, 10):
            my_thread = MyThread()
            my_thread.start()

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

    用RLock代替Lock,则不会发生死锁:

    	
    mutex = threading.RLock()
    

     Event对象

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    
    event.wait():如果 event.isSet()==False将阻塞线程;
    
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    
    event.clear():恢复event的状态值为False。
    
    import threading
    import time
    import logging
    
    logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s',)
    
    def worker(event):
        logging.debug('Waiting for redis ready...')
        event.wait()
        logging.debug('redis ready, and connect to redis server and do some work [%s]', time.ctime())
        time.sleep(1)
    
    def main():
        readis_ready = threading.Event()
        t1 = threading.Thread(target=worker, args=(readis_ready,), name='t1')
        t1.start()
    
        t2 = threading.Thread(target=worker, args=(readis_ready,), name='t2')
        t2.start()
    
        logging.debug('first of all, check redis server, make sure it is OK, and then trigger the redis ready event')
        time.sleep(3) # simulate the check progress
        readis_ready.set()
    
    if __name__=="__main__":
        main()

    threading.Event的wait方法还接受一个超时参数,默认情况下如果事件一致没有发生,wait方法会一直阻塞下去,而加入这个超时参数之后,如果阻塞时间超过这个参数设定的值之后,wait方法会返回。对应于上面的应用场景,如果Redis服务器一致没有启动,我们希望子线程能够打印一些日志来不断地提醒我们当前没有一个可以连接的Redis服务,我们就可以通过设置这个超时参数来达成这样的目的:

    def worker(event):
        while not event.is_set():
            logging.debug('Waiting for redis ready...')
            event.wait(2)
        logging.debug('redis ready, and connect to redis server and do some work [%s]', time.ctime())
        time.sleep(1)

    这样,我们就可以在等待Redis服务启动的同时,看到工作线程里正在等待的情况。

    Semaphore(信号量)

    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器-1;
    调用release() 时内置计数器+1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

    import threading
    import time
    
    semaphore = threading.Semaphore(5)
    
    def func():
        if semaphore.acquire():
            print (threading.currentThread().getName() + ' get semaphore')
            time.sleep(2)
            semaphore.release()
    
    for i in range(20):
      t1 = threading.Thread(target=func)
      t1.start()

    队列(queue)

    get与put方法

    '''
    
    创建一个“队列”对象
    
    import Queue
    q = Queue.Queue(maxsize = 10)
    Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数
    maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
    
    将一个值放入队列中
    q.put(10)
    调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;
    第二个block为可选参数,默认为
    1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,
    put方法将引发Full异常。
    
    将一个值从队列中取出
    q.get()
    调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且
    block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
    
    '''

    join与task_done方法

    '''
    join() 阻塞进程,直到所有任务完成,需要配合另一个方法task_done。
    
        def join(self):
         with self.all_tasks_done:
          while self.unfinished_tasks:
           self.all_tasks_done.wait()
    
    task_done() 表示某个任务完成。每一条get语句后需要一条task_done。
    
    
    import queue
    q = queue.Queue(5)
    q.put(10)
    q.put(20)
    print(q.get())
    q.task_done()
    print(q.get())
    q.task_done()
    
    q.join()
    
    print("ending!")
    '''

    其他常用方法

    '''
    
    此包中的常用方法(q = Queue.Queue()):
    
    q.qsize() 返回队列的大小
    q.empty() 如果队列为空,返回True,反之False
    q.full() 如果队列满了,返回True,反之False
    q.full 与 maxsize 大小对应
    q.get([block[, timeout]]) 获取队列,timeout等待时间
    q.get_nowait() 相当q.get(False)非阻塞 
    q.put(item) 写入队列,timeout等待时间
    q.put_nowait(item) 相当q.put(item, False)
    q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
    q.join() 实际上意味着等到队列为空,再执行别的操作
    
    '''

    其他模式

    '''
    
    Python Queue模块有三种队列及构造函数: 
    
    1、Python Queue模块的FIFO队列先进先出。  class queue.Queue(maxsize) 
    2、LIFO类似于堆,即先进后出。           class queue.LifoQueue(maxsize) 
    3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 
    
    
    import queue
    
    #先进后出
    
    q=queue.LifoQueue()
    
    q.put(34)
    q.put(56)
    q.put(12)
    
    #优先级
    q=queue.PriorityQueue()
    q.put([5,100])
    q.put([7,200])
    q.put([3,"hello"])
    q.put([4,{"name":"alex"}])
    
    while 1:
      data=q.get()
      print(data)
    
    '''

    生产者消费者模型

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

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

    这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。

    import time,random
    import queue,threading
    
    q = queue.Queue()
    
    def Producer(name):
      count = 0
      while count <10:
        print("making........")
        time.sleep(random.randrange(3))
        q.put(count)
        print('Producer %s has produced %s baozi..' %(name, count))
        count +=1
        #q.task_done()
        #q.join()
        print("ok......")
    def Consumer(name):
      count = 0
      while count <10:
        time.sleep(random.randrange(4))
        if not q.empty():
            data = q.get()
            #q.task_done()
            #q.join()
            print(data)
            print('33[32;1mConsumer %s has eat %s baozi...33[0m' %(name, data))
        else:
            print("-----no baozi anymore----")
        count +=1
    
    p1 = threading.Thread(target=Producer, args=('A',))
    c1 = threading.Thread(target=Consumer, args=('B',))
    # c2 = threading.Thread(target=Consumer, args=('C',))
    # c3 = threading.Thread(target=Consumer, args=('D',))
    p1.start()
    c1.start()
    # c2.start()
    # c3.start()

    multiprocessing模块

    由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

    multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

    python的进程调用

    # Process类调用
    
    from multiprocessing import Process
    import time
    def f(name):
    
        print('hello', name,time.ctime())
        time.sleep(1)
    
    if __name__ == '__main__':
        p_list=[]
        for i in range(3):
            p = Process(target=f, args=('alvin:%s'%i,))
            p_list.append(p)
            p.start()
        for i in p_list:
            p.join()
        print('end')
    
    # 继承Process类调用
    from multiprocessing import Process
    import time
    
    class MyProcess(Process):
        def __init__(self):
            super(MyProcess, self).__init__()
            # self.name = name
    
        def run(self):
    
            print ('hello', self.name,time.ctime())
            time.sleep(1)
    
    
    if __name__ == '__main__':
        p_list=[]
        for i in range(3):
            p = MyProcess()
            p.start()
            p_list.append(p)
    
        for p in p_list:
            p.join()
    
        print('end')

    process类

    构造方法:

    Process([group [, target [, name [, args [, kwargs]]]]])

      group: 线程组,目前还没有实现,库引用中提示必须是None; 
      target: 要执行的方法; 
      name: 进程名; 
      args/kwargs: 要传入方法的参数。

    实例方法:

      is_alive():返回进程是否在运行。

      join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

      start():进程准备就绪,等待CPU调度

      run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。

      terminate():不管任务是否完成,立即停止工作进程

    属性:

      daemon:和线程的setDeamon功能一样

      name:进程名字。

      pid:进程号。

    from multiprocessing import Process
    import os
    import time
    def info(name):
    
    
        print("name:",name)
        print('parent process:', os.getppid())
        print('process id:', os.getpid())
        print("------------------")
        time.sleep(1)
    
    def foo(name):
    
        info(name)
    
    if __name__ == '__main__':
    
        info('main process line')
    
    
        p1 = Process(target=info, args=('alvin',))
        p2 = Process(target=foo, args=('egon',))
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    
        print("ending")

    进程间通讯 

    进程对列Queue

    from multiprocessing import Process, Queue
    import queue
    
    def f(q,n):
        #q.put([123, 456, 'hello'])
        q.put(n*n+1)
        print("son process",id(q))
    
    if __name__ == '__main__':
        q = Queue()  #try: q=queue.Queue()
        print("main process",id(q))
    
        for i in range(3):
            p = Process(target=f, args=(q,i))
            p.start()
    
        print(q.get())
        print(q.get())
        print(q.get())

    管道(pipe)

    from multiprocessing import Process, Pipe
    
    def f(conn):
        conn.send([12, {"name":"yuan"}, 'hello'])
        response=conn.recv()
        print("response",response)
        conn.close()
        if __name__ == '__main__':
    
        parent_conn, child_conn = Pipe()
       
        p = Process(target=f, args=(child_conn,))
        p.start()
        print(parent_conn.recv())   # prints "[42, None, 'hello']"
        parent_conn.send("儿子你好!")
        p.join()

    Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(等等)。 请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,管道中的数据可能会损坏。

    manager

    Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据

    from multiprocessing import Process, Manager
    
    def f(d, l,n):
    
        d[n] = n
        d["name"] ="alvin"
        l.append(n)
    
        #print("l",l)
    
    if __name__ == '__main__':
    
        with Manager() as manager:
    
            d = manager.dict()
    
            l = manager.list(range(5))
            p_list = []
    
            for i in range(10):
                p = Process(target=f, args=(d,l,i))
                p.start()
                p_list.append(p)
    
            for res in p_list:
                res.join()
    
            print(d)
            print(l)
    
    复制代码
    进程池
    
    进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
    复制代码
    
    from multiprocessing import Pool
    import time
    
    def foo(args):
     time.sleep(1)
     print(args)
    
    if __name__ == '__main__':
     p = Pool(5)
     for i in range(30):
         p.apply_async(func=foo, args= (i,))
    
     p.close()   # 等子进程执行完毕后关闭线程池
     # time.sleep(2)
     # p.terminate()  # 立刻关闭线程池
     p.join()

    进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

    进程池中有以下几个主要方法:

    1. apply:从进程池里取一个进程并执行
    2. apply_async:apply的异步版本
    3. terminate:立刻关闭线程池
    4. join:主进程等待所有子进程执行完毕,必须在close或terminate之后
    5. close:等待所有进程结束后,才关闭线程池
  • 相关阅读:
    接口隔离原则(Interface Segregation Principle)ISP
    依赖倒置(Dependence Inversion Principle)DIP
    里氏替换原则(Liskov Substitution Principle) LSP
    单一指责原则(Single Responsibility Principle) SRP
    《面向对象葵花宝典》阅读笔记
    智能手表ticwatch穿戴体验
    我所理解的软件工程
    RBAC基于角色的权限访问控制
    程序员健康指南阅读笔记
    我晕倒在厕所了
  • 原文地址:https://www.cnblogs.com/xuzheng940806/p/6825297.html
Copyright © 2020-2023  润新知