进程之multiprocessing模块

Process(进程)

Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

  • 介绍

    • 初始化参数
      Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
      
      强调:
      1. 需要使用关键字的方式来指定参数
      2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
      
      参数介绍:
      1. group参数未使用,值始终为None
      2. target表示调用对象,即子进程要执行的任务
      3. args表示调用对象的位置参数元组,args=(1,2,'zze',)
      4. kwargs表示调用对象的字典,kwargs={'name':'zze','age':18}
      5. name为子进程的名称
    • 方法
      p.start():启动进程,并调用该子进程中的p.run()
      p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
      p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
      p.is_alive():如果p仍然运行,返回True
      p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
      
    • 属性
      p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
      p.name:进程的名称
      p.pid:进程的pid
      p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
      p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
    • 在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
  • 使用

    • 创建子进程
       import time
       from multiprocessing import Process
      
       def f(name):
           print('hello', name)
           print('我是子进程')
      
       if __name__ == '__main__':
           p = Process(target=f, args=('bob',))
           p.start()
           time.sleep(1)
           print('执行主进程的内容了')
      
       # result
       # hello bob
       # 我是子进程
       # 执行主进程的内容了
    • join()

      join()函数可以阻塞主进程,让其等待子进程代码执行完毕后,再执行join()后面的代码

       import time
       from multiprocessing import Process
      
       def f(name):
           print('hello', name)
           time.sleep(2)
           print('我是子进程')
      
       if __name__ == '__main__':
           p = Process(target=f, args=('bob',))
           p.start()
           time.sleep(1)
           p.join()
           print('执行主进程的内容了')
      
       # result
       # hello bob
       # 我是子进程
       # 执行主进程的内容了
    • 查看进程号
       import os
       from multiprocessing import Process
      
       def f(x):
           print('子进程id :',os.getpid(),'父进程id :',os.getppid())
           return x*x
      
       if __name__ == '__main__':
           print('主进程id :', os.getpid())
           p_lst = []
           for i in range(5):
               p = Process(target=f, args=(i,))
               p.start()
      
       #result:
       # 主进程id : 9208
       # 子进程id : 4276 父进程id : 9208
       # 子进程id : 3744 父进程id : 9208
       # 子进程id : 9392 父进程id : 9208
       # 子进程id : 3664 父进程id : 9208
       # 子进程id : 520 父进程id : 9208
    • 执行多个子进程
       import time
       from multiprocessing import Process
      
       def f(name):
           print('hello', name)
           time.sleep(1)
      
       if __name__ == '__main__':
           p_lst = []
           for i in range(5):
               p = Process(target=f, args=('bob%s'%i,))
               p.start()
               p_lst.append(p)
           [p.join() for p in p_lst]
           print('父进程在执行')
       #result:
       # hello bob0
       # hello bob1
       # hello bob2
       # hello bob3
       # hello bob4
       # 父进程在执行
    • 继承Process类开启进程
       import os
       from multiprocessing import Process
      
       class MyProcess(Process):
           def __init__(self, name):
               super().__init__()
               self.name = name
      
           def run(self):
               print(os.getpid())
               print(self.name)
      
       if __name__ == '__main__':
           p1 = MyProcess('p1')
           p2 = MyProcess('p2')
           p3 = MyProcess('p3')
           # start会自动调用run
           p1.start()
           p2.start()
           p3.start()
      
           p1.join()
           p2.join()
           p3.join()
           print('主线程111')
      
       #result:
      
       # p1
      
       # p2
      
       # p3
       # 主线程111
    • 守护进程
       import time
       from multiprocessing import Process
      
       def func():
           while True:
               print('我还活着')
               time.sleep(0.5)
      
       if __name__ == "__main__":
           p = Process(target=func)
           p.daemon = True  # 设置子进程为守护进程
           p.start()
           i = 2
           while i > 0:
               print('主进程执行')
               time.sleep(1)
               i -= 1
           print('主进程执行完毕')
      
       # result
       # 主进程执行
       # 我还活着
       # 我还活着
       # 主进程执行
       # 我还活着
       # 我还活着
       # 主进程执行完毕

      守护进程

       import time
       from multiprocessing import Process
      
       def func():
           while True:
               print('我还活着')
               time.sleep(0.5)
      
       if __name__ == "__main__":
           p = Process(target=func)
           # p.daemon = True  # 设置子进程为守护进程
           p.start()
           i = 2
           while i > 0:
               print('主进程执行')
               time.sleep(1)
               i -= 1
           print('主进程执行完毕')
      
       # result
       # 主进程执行
       # 我还活着
       # 我还活着
       # 主进程执行
       # 我还活着
       # 我还活着
       # 主进程执行完毕
       # 我还活着
       # 我还活着
       # 我还活着
       # 我还活着
       # 我还活着
       # 我还活着
       # 我还活着
       # 我还活着
       # ...

      非守护进程

Lock(锁)

加锁可以保证代码块在同一时间段只有指定一个进程执行

 from multiprocessing import Process
 import time
 import os

 def func():
     time.sleep(1)
     print('正在执行子进程的进程号:{},当前时间:{}'.format(os.getpid(), time.strftime("%Y-%m-%d %X")))

 if __name__ == '__main__':
     for i in range(5):
         Process(target=func).start()

 # result:
 # 正在执行子进程的进程号:6044,当前时间:2018-09-09 19:22:12
 # 正在执行子进程的进程号:7024,当前时间:2018-09-09 19:22:12
 # 正在执行子进程的进程号:9900,当前时间:2018-09-09 19:22:12
 # 正在执行子进程的进程号:8888,当前时间:2018-09-09 19:22:12
 # 正在执行子进程的进程号:10060,当前时间:2018-09-09 19:22:12

未加锁

 from multiprocessing import Lock
 from multiprocessing import Process
 import time
 import os

 def func(lock):
     lock.acquire()
     time.sleep(1)
     print('正在执行子进程的进程号:{},当前时间:{}'.format(os.getpid(), time.strftime("%Y-%m-%d %X")))
     lock.release()

 if __name__ == '__main__':
     lock = Lock()
     for i in range(5):
         Process(target=func, args=(lock,)).start()

 # result:
 # 正在执行子进程的进程号:8752,当前时间:2018-09-09 19:25:39
 # 正在执行子进程的进程号:10152,当前时间:2018-09-09 19:25:40
 # 正在执行子进程的进程号:5784,当前时间:2018-09-09 19:25:41
 # 正在执行子进程的进程号:9708,当前时间:2018-09-09 19:25:42
 # 正在执行子进程的进程号:8696,当前时间:2018-09-09 19:25:43

加锁

Semaphore(信号量)

信号量可以保证代码块在同一时间段只有指定数量进程执行

 from multiprocessing import Process, Semaphore
 import time

 def func(num, s):
     s.acquire()
     print('编号:{} 正在执行,'.format(num), time.strftime("%Y-%m-%d %X"))
     time.sleep(1)
     s.release()

 if __name__ == '__main__':
     s = Semaphore(2)
     for i in range(10):
         p = Process(target=func, args=(i, s))
         p.start()

 # result:
 # 编号:0 正在执行, 2018-09-10 16:16:28
 # 编号:1 正在执行, 2018-09-10 16:16:28
 # 编号:2 正在执行, 2018-09-10 16:16:29
 # 编号:3 正在执行, 2018-09-10 16:16:29
 # 编号:4 正在执行, 2018-09-10 16:16:30
 # 编号:5 正在执行, 2018-09-10 16:16:30
 # 编号:7 正在执行, 2018-09-10 16:16:31
 # 编号:6 正在执行, 2018-09-10 16:16:31
 # 编号:8 正在执行, 2018-09-10 16:16:32
 # 编号:9 正在执行, 2018-09-10 16:16:32

Event(事件)

例:让指定代码块在5秒后执行

 from multiprocessing import Process, Event
 import time

 # 获取指定秒数后的时间
 def get_addsec_time(sec=0):
     return time.strftime("%Y-%m-%d %X", time.localtime(time.time() + sec))

 def func(e):
     print('func准备执行')
     e.wait()  # 当e.is_set()为True时执行后面代码
     print('执行了,当前时间:{}'.format(time.strftime("%Y-%m-%d %X")))

 if __name__ == '__main__':
     e = Event()
     print(e.is_set())  # False 初始是阻塞状态
     e.set()
     print(e.is_set())  # True 不阻塞
     e.clear()
     print(e.is_set())  # False 恢复阻塞
     after_five_sec = get_addsec_time(5)  # 5秒后的时间
     Process(target=func, args=(e,)).start()
     while True:
         print('当前时间:{}'.format(time.strftime("%Y-%m-%d %X")))
         time.sleep(1)
         if time.strftime("%Y-%m-%d %X") == after_five_sec:
             print('5秒过去了')
             e.set()
             break;

 # result:
 # False
 # True
 # False
 # 当前时间:2018-09-10 17:06:37
 # func准备执行
 # 当前时间:2018-09-10 17:06:38
 # 当前时间:2018-09-10 17:06:39
 # 当前时间:2018-09-10 17:06:40
 # 当前时间:2018-09-10 17:06:41
 # 5秒过去了
 # 执行了,当前时间:2018-09-10 17:06:42

Queue(队列)

创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

  • 介绍

    • 初始化参数
      Queue([maxsize])
      创建共享的进程队列。
      参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
      底层队列使用管道和锁定实现。
    • 方法
      q.get( [ block [ ,timeout ] ] )
      返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
      
      q.get_nowait( )
      同q.get(False)方法。
      
      q.put(item [, block [,timeout ] ] )
      将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
      
      q.qsize()
      返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。
      
      q.empty()
      如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。
      
      q.full()
      如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。
      q.close()
      关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
      
      q.cancel_join_thread()
      不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。
      
      q.join_thread()
      连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
  • 使用

    • 队列用法
       '''
       multiprocessing模块支持进程间通信的两种主要形式:管道和队列
       都是基于消息传递实现的,但是队列接口
       '''
      
       from multiprocessing import Queue
       q=Queue(3)
      
       #put ,get ,put_nowait,get_nowait,full,empty
       q.put(3)
       q.put(3)
       q.put(3)
       # q.put(3)   # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。
                  # 如果队列中的数据一直不被取走,程序就会永远停在这里。
       try:
           q.put_nowait(3) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。
       except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。
           print('队列已经满了')
      
       # 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。
       print(q.full()) #满了
      
       print(q.get())
       print(q.get())
       print(q.get())
       # print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。
       try:
           q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。
       except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。
           print('队列已经空了')
      
       print(q.empty()) #空了
    • 子进程与主进程通行
       from multiprocessing import Process, Queue
      
       def func(q, e):
           q.put('from son process')
      
       if __name__ == '__main__':
           q = Queue(5)  # 初始化队列容量为5
           p = Process(target=func, args=(q))
           p.start()
           p.join()
           print(q.get())  # from son process

JoinableQueue(可连接队列)

创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

  • 介绍

    • 方法
      JoinableQueue的实例q除了与Queue对象相同的方法之外,还具有以下方法:
      
      q.task_done()
      使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
      
      q.join()
      生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。
      下面的例子说明如何建立永远运行的进程,使用和处理队列上的项目。生产者将项目放入队列,并等待它们被处理。
  • 使用

    • 生产者和消费者模型
       from multiprocessing import JoinableQueue, Process
       import time
       import random
      
       def producer(name, q):
           for i in range(1, 11):
               time.sleep(random.randint(1, 2))
               s = '{}生产的第{}个苹果'.format(name, i)
               q.put(s)
               print(s)
           q.join()  # 生产完毕,使用此方法进行阻塞,直到队列中所有苹果都被吃完。
      
       def consumer(name, q):
           while True:
               time.sleep(random.randint(2, 3))
               s = q.get()
               print('{}吃了{}'.format(name, s))
               q.task_done()  # 向q.join()发送一次信号,证明一个数据已经被取走了
      
       if __name__ == '__main__':
           q = JoinableQueue(10)
           producer_task = Process(target=producer, args=('bob', q))
           producer_task.start()
           consumer_task = Process(target=consumer, args=('tom', q))
           consumer_task.daemon = True  # 设置为守护进程 随主进程代码执行完而结束
           consumer_task.start()
      
           producer_task.join()  # 等待至生产完且生产的苹果都被吃完时继续执行即主进程代码结束
      
       # result:
       # bob生产的第1个苹果
       # tom吃了bob生产的第1个苹果
       # bob生产的第2个苹果
       # tom吃了bob生产的第2个苹果
       # bob生产的第3个苹果
       # tom吃了bob生产的第3个苹果
       # bob生产的第4个苹果
       # tom吃了bob生产的第4个苹果
       # bob生产的第5个苹果
       # tom吃了bob生产的第5个苹果
       # bob生产的第6个苹果
       # bob生产的第7个苹果
       # tom吃了bob生产的第6个苹果
       # bob生产的第8个苹果
       # tom吃了bob生产的第7个苹果
       # bob生产的第9个苹果
       # bob生产的第10个苹果
       # tom吃了bob生产的第8个苹果
       # tom吃了bob生产的第9个苹果
       # tom吃了bob生产的第10个苹果

Pipe(管道)

  • 介绍

    • 初始化参数
      #创建管道的类:(管道是进程不安全的)
      Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
      #参数介绍:
      dumplex:默认管道是全双工的,如果将duplex设成False,conn1只能用于接收,conn2只能用于发送。
    • 方法
      #主要方法:
      conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
      conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
      #其他方法:
      conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
      conn1.fileno():返回连接使用的整数文件描述符
      conn1.poll([timeout]):如果连接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout射成None,操作将无限期地等待数据到达。
      
      conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息,超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
      conn.send_bytes(buffer [, offset [, size]]):通过连接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收    
      
      conn1.recv_bytes_into(buffer [, offset]):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
  • 使用

    • 初使用
       from multiprocessing import Process, Pipe
      
       def f(conn):
           conn.send("from sub process")
           conn.close()
      
       if __name__ == '__main__':
           parent_conn, child_conn = Pipe()
           p = Process(target=f, args=(child_conn,))
           p.start()
           print(parent_conn.recv())  # from sub process
           p.join()
    • 引发EOFError

      应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。

       from multiprocessing import Process, Pipe
      
       def f(child_conn):
           while True:
               try:
                   print(child_conn.recv())
               except EOFError:
                   child_conn.close()
                   break
      
       if __name__ == '__main__':
           parent_conn, child_conn = Pipe()
           p = Process(target=f, args=(child_conn,))
           p.start()
           child_conn.close()
           parent_conn.send('hello')
           parent_conn.close()
           p.join()

Manager

 from multiprocessing import Manager, Process, Lock

 def work(d, lock):
     # with lock:
         d['count'] -= 1

 if __name__ == '__main__':
     with Manager() as m:
         lock = Lock()
         dic = m.dict({'count': 100})
         p_l = []
         for i in range(10):
             p = Process(target=work, args=(dic, lock))
             p_l.append(p)
             p.start()
         for p in p_l: p.join()

         print(dic)  # {'count': 91}
 # Manager包装的类型是进程不安全的

使用

Pool(进程池)

  • 介绍

    • 初始化参数
      Pool([numprocess  [,initializer [, initargs]]]):创建进程池
      numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
      initializer:是每个工作进程启动时要执行的可调用对象,默认为None
      initargs:是要传给initializer的参数组
    • 方法
      p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
      '''需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()'''
      
      p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
      '''此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。'''
      
      p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
      
      P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
      

      方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
       obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
       obj.ready():如果调用完成,返回True
       obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
       obj.wait([timeout]):等待结果变为可用。
       obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数

  • 使用

    • 效率对比
       from multiprocessing import Pool, Process
       import time
      
       def func(n):
           for i in range(100):
               n += i
      
       if __name__ == '__main__':
           pool = Pool(5)
           start = time.time()
           pool.map(func, range(100))
           print('进程池执行耗时:{}'.format(time.time() - start))
           p_list = []
           start = time.time()
           for i in range(100):
               p = Process(target=func, args=(i,))
               p.start()
               p_list.append(p)
           for p in p_list: p.join()
           print('多进程执行耗时:{}'.format(time.time() - start))
      
       # result:
           # 进程池执行耗时: 0.24797534942626953
           # 多进程执行耗时: 7.359263896942139
    • 同步
       import os, time
       from multiprocessing import Pool
      
       def work(n):
           print('%s run' % os.getpid())
           time.sleep(3)
           return n ** 2
      
       if __name__ == '__main__':
           p = Pool(3)  # 进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
           res_l = []
           for i in range(10):
               res = p.apply(work, args=(i,))  # 同步调用,直到本次任务执行完毕拿到res,等待任务work执行的过程中可能有阻塞也可能没有阻塞
               res_l.append(res)  # 但不管该任务是否存在阻塞,同步调用都会在原地等着
           print(res_l)
       # result:
           # 15940 run
           # 16200 run
           # 16320 run
           # 15940 run
           # 16200 run
           # 16320 run
           # 15940 run
           # 16200 run
           # 16320 run
           # 15940 run
           # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    • 异步
       import os
       import time
       import random
       from multiprocessing import Pool
      
       def work(n):
           print('%s run' % os.getpid())
           time.sleep(random.random())
           return n ** 2
      
       if __name__ == '__main__':
           p = Pool(3)  # 进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
           res_l = []
           for i in range(10):
               res = p.apply_async(work, args=(i,))  # 异步运行,根据进程池中有的进程数,每次最多3个子进程在异步执行
               # 返回结果之后,将结果放入列表,归还进程,之后再执行新的任务
               # 需要注意的是,进程池中的三个进程不会同时开启或者同时结束
               # 而是执行完一个就释放一个进程,这个进程就去接收新的任务。
               res_l.append(res)
      
           # 异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果
           # 否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
           p.close()
           p.join()
           for res in res_l:
               print(res.get())  # 使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
      
       # result:
           # 8872 run
           # 13716 run
           # 11396 run
           # 11396 run
           # 8872 run
           # 13716 run
           # 11396 run
           # 8872 run
           # 13716 run
           # 11396 run
      
    • 回调函数
       from multiprocessing import Pool
      
       def func(i):
           return i * i
      
       def callback_func(i):
           print(i)
      
       if __name__ == '__main__':
           pool = Pool(5)
           for i in range(1, 11):
               pool.apply_async(func, args=(i,), callback=callback_func)
           pool.close()
           pool.join()
       # # result:
      

线程之threading模块

进程和线程的关系

1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

3)调度和切换:线程上下文切换比进程上下文切换要快得多。

4)在多线程操作系统中,进程不是一个可执行的实体。

5)进程是资源分配的最小单位,线程是CPU调度的最小单位,每一个进程中至少有一个线程。

Thread(线程)

  • 介绍

    multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。

    Thread实例对象的方法
      isAlive(): 返回线程是否活动的。
      getName(): 返回线程名。
      setName(): 设置线程名。
    
    threading模块提供的一些方法:
      threading.currentThread(): 返回当前的线程变量。
      threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
     import threading
     import time
    
     def func():
         print('start sub thread1')
         print(threading.currentThread())  # <Thread(sub thread1, started 11832)>
         time.sleep(10)
         print('end sub thread1')
    
     thread = threading.Thread(target=func)
     thread.start()
     print(thread.is_alive())  # True
     print(thread.getName())  # Thread-1
     thread.setName('sub thread1')
     print(thread.getName())  # sub thread1
     print(threading.currentThread())  # <_MainThread(MainThread, started 9708)>
     print(threading.enumerate())  # [<_MainThread(MainThread, started 9708)>, <Thread(sub thread1, started 11832)>]
     

    示例

  • 使用

    • 创建线程
       from threading import Thread
      
       def func():
           print('from sub threading')
      
       p = Thread(target=func)
       p.start()
       p.join()
       # result:
           # from sub threading
    • 继承Thread创建线程
       from threading import Thread
      
       class MyThread(Thread):
           def run(self):
               print('from sub thread,threadid:{}'.format(self.ident))
      
       my_thread = MyThread()
       my_thread.start()
       my_thread.join()
      
       # result:
           # from sub thread,threadid:9332
    • 数据共享

      同一进程内的线程之间共享进程内的数据

       from threading import Thread
      
       def func():
           global i
           i = 1
      
       i = 10
       thread = Thread(target=func)
       thread.start()
       thread.join()
       
    • 守护线程与守护进程的对比

      无论是进程还是线程,都遵循:守护进程/线程会等待主进程/线程运行完毕后被销毁。需要强调的是:运行完毕并非终止运行

      1.对主进程来说,运行完毕指的是主进程代码运行完毕

      2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

       from multiprocessing import Process
       import time
      
       def notDaemonFunc():
           print('start notDaemonFunc')
           time.sleep(10)
           print('end notDaemonFunc')
      
       def daemonFunc():
           print('start daemonFunc')
           time.sleep(5)
           print('end daemonFunc')  # 主进程代码早已执行完毕没机会执行
      
       if __name__ == '__main__':
           notDaemonProcess = Process(target=notDaemonFunc)
           notDaemonProcess.start()
           damonProcess = Process(target=daemonFunc)
           damonProcess.daemon = True
           damonProcess.start()
           time.sleep(1)
           print('执行完毕')
      
       # 主进程代码执行完毕时守护进程立马结束
      
       # result:
           # start notDaemonFunc
           # start daemonFunc
           # 执行完毕
           # end notDaemonFunc

      守护进程

       from threading import Thread
       import time
      
       def notDaemonFunc():
           print('start notDaemonFunc')
           time.sleep(10)
           print('end notDaemonFunc')
      
       def daemonFunc():
           print('start daemonFunc')
           time.sleep(5)
           print('end daemonFunc')
      
       notDaemonThread = Thread(target=notDaemonFunc)
       notDaemonThread.start()
       damonThread = Thread(target=daemonFunc)
       damonThread.daemon = True
       damonThread.start()
       time.sleep(1)
       print('执行完毕')
      
       # result:
           # start notDaemonFunc
           # start daemonFunc
           # 执行完毕
           # end daemonFunc
           # end notDaemonFunc

      守护线程

Lock(锁)

  • 同步锁
     from threading import Thread
     import time
    
     def work():
         global n
         temp = n
         time.sleep(0.1)
         n = temp - 1
    
     if __name__ == '__main__':
         n = 100
         l = []
         for i in range(100):
             p = Thread(target=work)
             l.append(p)
             p.start()
         for p in l:
             p.join()
    
         print(n)  # 期望0 但结果可能为99 98

    未加锁

     from threading import Thread, Lock
     import time
    
     def work(lock):
         with lock:
             global n
             temp = n
             time.sleep(0.1)
             n = temp - 1
    
     if __name__ == '__main__':
         n = 100
         l = []
         lock = Lock()
         for i in range(100):
             p = Thread(target=work, args=(lock,))
             l.append(p)
             p.start()
         for p in l:
             p.join()
    

    加锁

  • 死锁

    是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

     import time
     from threading import Thread, Lock
    
     noodle_lock = Lock()
     fork_lock = Lock()
    
     def eat1(name):
         noodle_lock.acquire()
         print('%s 抢到了面条' % name)
         time.sleep(1)
         fork_lock.acquire()
         print('%s 抢到了筷子' % name)
         print('%s 吃面' % name)
         fork_lock.release()
         noodle_lock.release()
    
     def eat2(name):
         fork_lock.acquire()
         print('%s 抢到了筷子' % name)
         time.sleep(1)
         noodle_lock.acquire()
         print('%s 抢到了面条' % name)
         print('%s 吃面' % name)
         noodle_lock.release()
         fork_lock.release()
    
     t1 = Thread(target=eat1, args=('tom',))
     t2 = Thread(target=eat2, args=('jerry',))
     t1.start()
     t2.start()
     #result:
         # tom 抢到了面条
         # jerry 抢到了叉子

    吃面例子

  • 死锁的解决-递归锁

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

     import time
     from threading import Thread, RLock
    
     fork_lock = noodle_lock = RLock()
    
     def eat1(name):
         noodle_lock.acquire()
         print('%s 抢到了面条' % name)
         time.sleep(1)
         fork_lock.acquire()
         print('%s 抢到了筷子' % name)
         print('%s 吃面' % name)
         fork_lock.release()
         noodle_lock.release()
    
     def eat2(name):
         fork_lock.acquire()
         print('%s 抢到了筷子' % name)
         time.sleep(1)
         noodle_lock.acquire()
         print('%s 抢到了面条' % name)
         print('%s 吃面' % name)
         noodle_lock.release()
         fork_lock.release()
    
     t1 = Thread(target=eat1, args=('tom',))
     t2 = Thread(target=eat2, args=('jerry',))
     t1.start()
     t2.start()
    
     # result:
         # tom 抢到了面条
         # tom 抢到了筷子
         # tom 吃面
         # jerry 抢到了筷子
         # jerry 抢到了面条
         # jerry 吃面

    解决吃面问题

Semaphore(信号量)

 from threading import Thread, Semaphore
 import time

 def func(num, s):
     s.acquire()
     print('编号:{} 正在执行,'.format(num), time.strftime("%Y-%m-%d %X"))
     time.sleep(1)
     s.release()

 s = Semaphore(2)
 [Thread(target=func, args=(i, s)).start() for i in range(10)]

 # result:
     # 编号:0 正在执行, 2018-09-12 20:33:09
     # 编号:1 正在执行, 2018-09-12 20:33:09
     # 编号:2 正在执行, 2018-09-12 20:33:10
     # 编号:3 正在执行, 2018-09-12 20:33:10
     # 编号:4 正在执行, 2018-09-12 20:33:11
     # 编号:5 正在执行, 2018-09-12 20:33:11
     # 编号:7 正在执行, 2018-09-12 20:33:12
     # 编号:6 正在执行, 2018-09-12 20:33:12
     # 编号:9 正在执行, 2018-09-12 20:33:13
     # 编号:8 正在执行, 2018-09-12 20:33:13

Event(事件)

 from threading import Thread, Event
 import time

 # 获取指定秒数后的时间
 def get_addsec_time(sec=0):
     return time.strftime("%Y-%m-%d %X", time.localtime(time.time() + sec))

 def func(e):
     print('func准备执行')
     e.wait()  # 当e.is_set()为True时执行后面代码
     print('执行了,当前时间:{}'.format(time.strftime("%Y-%m-%d %X")))

 e = Event()
 print(e.is_set())  # False 初始是阻塞状态
 e.set()
 print(e.is_set())  # True 不阻塞
 e.clear()
 print(e.is_set())  # False 恢复阻塞
 after_five_sec = get_addsec_time(5)  # 5秒后的时间
 Thread(target=func, args=(e,)).start()
 while True:
     print('当前时间:{}'.format(time.strftime("%Y-%m-%d %X")))
     time.sleep(1)
     if time.strftime("%Y-%m-%d %X") == after_five_sec:
         print('5秒过去了')
         e.set()
         break;

 # result:
     # False
     # True
     # False
     # func准备执行
     # 当前时间:2018-09-12 20:37:27
     # 当前时间:2018-09-12 20:37:28
     # 当前时间:2018-09-12 20:37:29
     # 当前时间:2018-09-12 20:37:30
     # 当前时间:2018-09-12 20:37:31
     # 5秒过去了
     # 执行了,当前时间:2018-09-12 20:37:32

Condition(条件)

使得线程等待,只有满足某条件时,才释放n个线程

Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

 import threading

 def run(n):
     con.acquire()
     print('prepare')
     con.wait()
     print("run the thread: %s" % n)
     con.release()

 con = threading.Condition()
 for i in range(5):
     t = threading.Thread(target=run, args=(i,))
     t.start()

 while True:
     inp = input('>>>')
     if inp == 'q':
         break
     con.acquire()
     con.notify(int(inp))
     con.release()
     print('------------------------')

 #result:
     # prepare
     # prepare
     # prepare
     # prepare
     # prepare
     # >>>3
     # ------------------------
     # run the thread: 2
     # run the thread: 1
     # run the thread: 0
     # >>>3
     # ------------------------
     # run the thread: 4
     # run the thread: 3
     # >>>q

Timer(定时器)

指定n秒后执行某个函数

 from threading import Timer
 import time

 def func():
     print('in func,current time:{}'.format(time.strftime('%X')))

 print('in main,current time:{}'.format(time.strftime('%X')))
 # 5秒后执行
 t = Timer(5, func)
 t.start()

 # result:
     # in main,current time:20:53:52
     # in func,current time:20:53:57

扩展

queen模块

在上述threading模块知识点中并没有出现一个和multiprocessing模块中Queen对应的队列,这是因为python本身给我们提供的queen就是线程安全的,而同个进程的线程之间资源是可以共享的,所以我们可以直接使用queen

  • queue.Queue(maxsize=0) 先进先出

     import queue
    
     q=queue.Queue()
     q.put('first')
     q.put('second')
     q.put('third')
    
     print(q.get())
     print(q.get())
     print(q.get())
    
     '''
     result:
         first
         second
         third
     '''
  • queue.LifoQueue(maxsize=0) 后进先出
     import queue
    
     q = queue.LifoQueue()
     q.put('first')
     q.put('second')
     q.put('third')
    
     print(q.get())
     print(q.get())
     print(q.get())
     '''
     result:
         third
         second
         first
     '''
  • queue.PriorityQueue(maxsize=0) #优先级
     import queue
    
     q = queue.PriorityQueue()
     # put进入一个元组,第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
     q.put((20, 'a'))
     q.put((10, 'b'))
     q.put((30, 'c'))
    
     print(q.get())
     print(q.get())
     print(q.get())
     '''
     数字越小优先级越高,优先级高的优先出队
     result:
         (10, 'b')
         (20, 'a')
         (30, 'c')
     '''

线程池之concurrent.futures模块

  • 介绍

    concurrent.futures模块提供了高度封装的异步调用接口
    ThreadPoolExecutor:线程池,提供异步调用
    ProcessPoolExecutor:进程池,提供异步调用
    
    executor = ProcessPoolExecutor(max_workers=n):初始化进程池 max_workers指定池内最大进程数
    executor.submit(fn, *args, **kwargs):异步提交任务
    executor.map(func, *iterables, timeout=None, chunksize=1) 取代for循环submit的操作
    executor.shutdown(wait=True)  :相当于multiprocessing模块中的pool.close()+pool.join()操作,wait=True时,等待池内所有任务执行完毕回收完资源后才继续.wait=False时,立即返回,并不会等待池内的任务执行完毕,但不管wait参数为何值,整个程序都会等到所有任务执行完毕,submit和map必须在shutdown之前
    executor.submit().result(timeout=None):取得结果
    executor.submit().result(timeout=None):取得结果
    executor.submit().add_done_callback(fn):给任务添加回调函数
  • 使用

    • 创建进程池
       from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
      
       import os, time
      
       def func(n):
           print('{} is runing ,current time:{}'.format(os.getpid(), time.strftime('%X')))
           time.sleep(1)
           return 'pid:{} finished'.format(os.getpid())
      
       if __name__ == '__main__':
           executor = ProcessPoolExecutor(max_workers=2)
           result_list = []
           for i in range(1, 6):
               result = executor.submit(func, i)
               result_list.append(result)
           executor.shutdown(True)
           print('---------------get result-----------------')
           for result in result_list:
               print(result.result())
      
       '''
       result:
           3444 is runing ,current time:21:32:39
           2404 is runing ,current time:21:32:39
           3444 is runing ,current time:21:32:40
           2404 is runing ,current time:21:32:40
           3444 is runing ,current time:21:32:41
           ---------------get result-----------------
           pid:3444 finished
           pid:2404 finished
           pid:3444 finished
           pid:2404 finished
           pid:3444 finished
       '''
    • map使用
       from concurrent.futures import ThreadPoolExecutor
       import threading
       import time
      
       def task(n):
           print('threadId:{} is runing,current time:{}'.format(threading.currentThread().ident, time.strftime('%X')))
           time.sleep(1)
           return n ** 2
      
       if __name__ == '__main__':
           executor = ThreadPoolExecutor(max_workers=2)
      
           # for i in range(11):
           #     future=executor.submit(task,i)
      
           executor.map(task, range(1, 5))  # map取代了for+submit
      
       '''
       result:
           threadId:5324 is runing,current time:21:53:24
           threadId:3444 is runing,current time:21:53:24
           threadId:5324 is runing,current time:21:53:25
           threadId:3444 is runing,current time:21:53:25
       '''
    • 回调函数
       from concurrent.futures import ThreadPoolExecutor
       import threading
       import time
      
       def callback_func(result):
           print(result.result())
      
       def func(i):
           return i * i
      
       executor = ThreadPoolExecutor(5)
       [executor.submit(func, i).add_done_callback(callback_func) for i in range(1, 5)]
      
       '''
       result:
           1
           4
           9
           16
       '''

协程之gevent模块

单线程里执行多个任务代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。如此,才能提高效率,这就用到了Gevent模块。

介绍

  • 简介

    协程是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

    需要强调的是:

    1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)

    2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关) 对比操作系统控制线程的切换,用户在单线程内控制协程的切换

  • 优点

    1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

    2. 单线程内就可以实现并发的效果,最大限度地利用cpu

  • 缺点

    1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

    2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

  • 特点

    1. 必须在只有一个单线程里实现并发

    2. 修改共享数据不需加锁

    3. 用户程序里自己保存多个控制流的上下文栈

    4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

greenlet模块

安装: pip3 install greenlet

  • 实现状态切换

     from greenlet import greenlet
    
     def func1():
         print('func1 start')
         g2.switch()
         print('func1 end')
         g2.switch()
    
     def func2():
         print('func2 start')
         g1.switch()
         print('func2 end')
    
     g1 = greenlet(func1)
     g2 = greenlet(func2)
     g1.switch()
    
     '''
     result:
         func1 start
         func2 start
         func1 end
         func2 end
     '''
  • 顺序执行与切换执行效率对比

     #顺序执行
     import time
     def f1():
         res=1
         for i in range(100000000):
             res+=i
    
     def f2():
         res=1
         for i in range(100000000):
             res*=i
    
     start=time.time()
     f1()
     f2()
     stop=time.time()
     print('run time is %s' %(stop-start)) #10.985628366470337
    
     #切换
     from greenlet import greenlet
     import time
     def f1():
         res=1
         for i in range(100000000):
             res+=i
             g2.switch()
    
     def f2():
         res=1
         for i in range(100000000):
             res*=i
             g1.switch()
    
     start=time.time()
     g1=greenlet(f1)
     g2=greenlet(f2)
     g1.switch()
     stop=time.time()
     print('run time is %s' %(stop-start)) # 52.763017892837524

    单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

协程初使用

安装: pip3 install gevent

  • 非协程和协程耗时对比

     import gevent
     import threading
     import os
     import time
    
     def func1():
         print('pid:{} threadid:{} from func1 | start'.format(os.getpid(), threading.get_ident()))
         gevent.sleep(1)
         print('pid:{} threadid:{} from func1 | end'.format(os.getpid(), threading.get_ident()))
    
     def func2():
         print('pid:{} threadid:{} from func2 | start'.format(os.getpid(), threading.get_ident()))
         gevent.sleep(1)
         print('pid:{} threadid:{} from func2 | end'.format(os.getpid(), threading.get_ident()))
    
     start = time.time()
     func1()
     func2()
     print('非协程耗时:{}'.format(time.time() - start))
    
     start = time.time()
     g1 = gevent.spawn(func1)
     g2 = gevent.spawn(func2)
     g1.join()
     g2.join()
     print('协程耗时:{}'.format(time.time() - start))
    
     '''
     result:
         pid:12092 threadid:2828 from func1 | start
         pid:12092 threadid:2828 from func1 | end
         pid:12092 threadid:2828 from func2 | start
         pid:12092 threadid:2828 from func2 | end
         非协程耗时:2.008000135421753
         pid:12092 threadid:2828 from func1 | start
         pid:12092 threadid:2828 from func2 | start
         pid:12092 threadid:2828 from func1 | end
         pid:12092 threadid:2828 from func2 | end
         协程耗时:1.0
     '''
  • monkey-识别io阻塞

    上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

    from gevent import monkey;monkey.patch_all() # 必须放到被打补丁者的前面
     from gevent import monkey;monkey.patch_all()
     import gevent
     import threading
     import os
     import time
    
     def func1():
         print('pid:{} threadid:{} from func1 | start'.format(os.getpid(), threading.get_ident()))
         time.sleep(1)
         print('pid:{} threadid:{} from func1 | end'.format(os.getpid(), threading.get_ident()))
    
     def func2():
         print('pid:{} threadid:{} from func2 | start'.format(os.getpid(), threading.get_ident()))
         time.sleep(1)
         print('pid:{} threadid:{} from func2 | end'.format(os.getpid(), threading.get_ident()))
    
     start = time.time()
     func1()
     func2()
     print('非协程耗时:{}'.format(time.time() - start))
    
     start = time.time()
     g1 = gevent.spawn(func1)
     g2 = gevent.spawn(func2)
     g1.join()
     g2.join()
     print('协程耗时:{}'.format(time.time() - start))
    
     '''
     result:
         pid:7200 threadid:43458064 from func1 | start
         pid:7200 threadid:43458064 from func1 | end
         pid:7200 threadid:43458064 from func2 | start
         pid:7200 threadid:43458064 from func2 | end
         非协程耗时:2.004999876022339
         pid:7200 threadid:55386728 from func1 | start
         pid:7200 threadid:55387544 from func2 | start
         pid:7200 threadid:55386728 from func1 | end
         pid:7200 threadid:55387544 from func2 | end
         协程耗时:1.000999927520752
     '''
  • 统计网页长度

     from gevent import monkey;monkey.patch_all()
     import gevent
     import requests
     import time
    
     def get_page(url):
         print('GET: %s' % url)
         response = requests.get(url)
         if response.status_code == 200:
             print('%d bytes received from %s' % (len(response.text), url))
    
     start_time = time.time()
     gevent.joinall([
         gevent.spawn(get_page, 'https://www.python.org/'),
         gevent.spawn(get_page, 'https://www.yahoo.com/'),
         gevent.spawn(get_page, 'https://github.com/'),
     ])
     stop_time = time.time()
     print('run time is %s' % (stop_time - start_time))
     '''
     result:
         GET: https://www.python.org/
         GET: https://www.yahoo.com/
         GET: https://github.com/
         64127 bytes received from https://github.com/
         48854 bytes received from https://www.python.org/
         502701 bytes received from https://www.yahoo.com/
         run time is 1.9760000705718994
     '''
  • 单线程下的socket并发

     from gevent import monkey;monkey.patch_all()
     from socket import *
     import gevent
    
     # 如果不想用money.patch_all()打补丁,可以用gevent自带的socket
     # from gevent import socket
     # s=socket.socket()
    
     def server(server_ip, port):
         s = socket(AF_INET, SOCK_STREAM)
         s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
         s.bind((server_ip, port))
         s.listen(5)
         while True:
             conn, addr = s.accept()
             gevent.spawn(talk, conn, addr)
    
     def talk(conn, addr):
         try:
             while True:
                 res = conn.recv(1024)
                 print('client %s:%s msg: %s' % (addr[0], addr[1], res))
                 conn.send(res.upper())
         except Exception as e:
             print(e)
         finally:
             conn.close()
    
     if __name__ == '__main__':
         server('127.0.0.1', 8080)

    server

     from threading import Thread
     from socket import *
     import threading
    
     def client(server_ip, port):
         c = socket(AF_INET, SOCK_STREAM)  # 套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
         c.connect((server_ip, port))
    
         count = 0
         while True:
             c.send(('%s say hello %s' % (threading.current_thread().getName(), count)).encode('utf-8'))
             msg = c.recv(1024)
             print(msg.decode('utf-8'))
             count += 1
    
     if __name__ == '__main__':
         for i in range(500):
             t = Thread(target=client, args=('127.0.0.1', 8080))
             t.start()

    client

python基础(16)-进程&线程&协程的更多相关文章

  1. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  2. Python并发编程系列之常用概念剖析:并行 串行 并发 同步 异步 阻塞 非阻塞 进程 线程 协程

    1 引言 并发.并行.串行.同步.异步.阻塞.非阻塞.进程.线程.协程是并发编程中的常见概念,相似却也有却不尽相同,令人头痛,这一篇博文中我们来区分一下这些概念. 2 并发与并行 在解释并发与并行之前 ...

  3. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  4. 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型

    本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...

  5. 进程&线程&协程

    进程  一.基本概念 进程是系统资源分配的最小单位, 程序隔离的边界系统由一个个进程(程序)组成.一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stac ...

  6. Python 多线程、进程、协程上手体验

    浅谈 Python 多线程.进程.协程上手体验 前言:浅谈 Python 很多人都认为 Python 的多线程是垃圾(GIL 说这锅甩不掉啊~):本章节主要给你体验下 Python 的两个库 Thre ...

  7. python并发编程之线程/协程

    python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...

  8. python的进程/线程/协程

    1.python的多线程 多线程就是在同一时刻执行多个不同的程序,然而python中的多线程并不能真正的实现并行,这是由于cpython解释器中的GIL(全局解释器锁)捣的鬼,这把锁保证了同一时刻只有 ...

  9. python-socket和进程线程协程(代码展示)

    socket # 一.socket # TCP服务端 import socket # 导入socket tcp_sk = socket.socket() # 实例化一个服务器对象 tcp_sk.bin ...

随机推荐

  1. Atitit s2018.2 s2 doc list on home ntpc.docx  \Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别 讯飞科大 语音云.docx \Atitit 代码托管与虚拟主机.docx \Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx \Atitit 几大研发体系对比 Stage-Gat

    Atitit s2018.2 s2 doc list on home ntpc.docx \Atiitt uke制度体系  法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别   ...

  2. [HDFS Manual] CH7 ViewFS Guide

    ViewFS Guide ViewFS Guide 1 介绍 2. The Old World(Prior to Federation) 2.1单个Namenode Clusters 2.2 路径使用 ...

  3. VS2015 怎么安装RDLC报表模板?

    这几天刚好用到微软自带的RDLC报表,但是在VS2015张找了一圈也没找,难道是我VS版本 不支持,在网上查了下,有的人说VS2015社区版,企业版不支持,只有专业版支持,各说不一,想想不科学呀,微软 ...

  4. Git进阶用法

    Git高阶用法 1. 基本概念 你的本地仓库由Git维护的三棵树组成.第一个是你的工作目录,它持有实际文件:第二个是缓存区(Index),它像个缓存区域,临时保存您的改动:最后是HEAD,指向你最近一 ...

  5. Golang学习教程

    字节跳动已经全线从Python转Golang了,可能开始学习Golang这门语言会觉得无所适从,和Java,C++,Python等都不大一样,但是用多了会发现这门语言设计的还是很优雅的,下面总结Gol ...

  6. redis安装相关下载

    redis-4.0.1.gem下载网址 https://rubygems.org/gems/redis/ rubyinstaller-2.3.3-x64.exe下载网址 http://dl.bintr ...

  7. Mybatis常考面试题汇总(附答案)

    1.#{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是预编译处理,${}是字 ...

  8. Java不区分大小写的CaseInsensitiveMap

    Java中对于键值对,我们习惯使用类HashMap,使用方式:Map<String, String> result=new HashMap<String,String>(); ...

  9. 同时使用Union和Order by问题(ORA-00933错误)解决

    之前,同事在编写视图的过程中遇到这样了这个错误.我把简化后的语句整理如下: 1: select 2: '2016' as nf, 3: qxdm, 4: round(sum(tbdlmj)/10000 ...

  10. C#GDI+ 绘制线段(实线或虚线)、矩形、字符串、圆、椭圆

    C#GDI+ 绘制线段(实线或虚线).矩形.字符串.圆.椭圆 绘制基本线条和图形 比较简单,直接看代码. Graphics graphics = e.Graphics; //绘制实线 )) { pen ...