python# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selector

# 进程/线程/协程
# IO:同步/异步/阻塞/非阻塞
#  greenlet gevent
# 事件驱动与异步IO
# Select\Poll\Epoll异步IO 以及selectors模块
# Python队列/RabbitMQ队列  

##############################################################################################
1.什么是进程?进程和程序之间有什么区别?
   进程:一个程序的执行实例称为进程;
   每个进程都提供执行程序所需的资源。
   进程有一个虚拟地址空间、可执行代码、对系统对象的开放句柄、一个安全上下文、一个惟一的进程标识符、环境变量、一个优先级类、最小和最大工作集大小,以及至少一个执行线程;
   每个进程都由一个线程启动,这个线程通常被称为主线程,但是可以从它的任何线程中创建额外的线程;
   程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程;
   程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
   在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行,大大提高了CPU的利用率
2.什么是线程?
   进程的缺点有:
      进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
      进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
   线程是操作系统能够进行运算调度的最小单位。
   它被包含在进程之中,是进程中的实际运作单位。
   一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
   线程是一个执行上下文,它是一个CPU用来执行指令流的所有信息。
3.进程和线程之间的关系?
   线程共享创建它的进程的地址空间;进程有自己的地址空间。(内存地址)
   线程可以直接访问其进程的数据段;进程有自己的父进程数据段的副本。
   线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。
   新线程很容易创建;新进程需要复制父进程。
   线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。
   对主线程的更改(取消、优先级更改等)可能会影响流程的其他线程的行为;对父进程的更改不会影响子进程。
4.python GIL全局解释器锁
   无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
   http: // www.dabeaz.com / python / UnderstandingGIL.pdf
5.Python threading模块的使用
   基本调用方式1
      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())
   基本调用方式2
      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()
6.守护线程Daemon:
   非守护进程线程退出,就可以将守护线程杀死。
   # _*_coding:utf-8_*_

   import time
   import threading


   def run(n):
      print('[%s]------running----\n' % n)
      time.sleep(2)
      print('--done--')


   def main():
      for i in range(5):
         t = threading.Thread(target=run, args=[i, ])
         t.start()
         t.join(1)
         print('starting thread', t.getName())


   m = threading.Thread(target=main, args=[])
   m.setDaemon(True)  # main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,m启动的其它子线程会同时退出,不管是否执行完任务
   m.start()
   m.join(timeout=2)
   print("---main thread done----")
7.线程锁(互斥锁)
   一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,可能会导致数据被同时修改而使得计算结果不准确(重复赋值)
   import time
   import threading


   def addNum():
      global num  # 在每个线程中都获取这个全局变量
      print('--get num:', num)
      time.sleep(1)
      num -= 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('final num:', num)
   加上线程锁
   import time
   import threading


   def addNum():
      global num  # 在每个线程中都获取这个全局变量
      print('--get num:', num)
      time.sleep(1)
      lock.acquire()  # 修改数据前加锁
      num -= 1  # 对此公共变量进行-1操作
      lock.release()  # 修改后释放


   num = 100  # 设定一个共享变量
   thread_list = []
   lock = threading.Lock()  # 生成全局锁
   for i in range(100):
      t = threading.Thread(target=addNum)
      t.start()
      thread_list.append(t)

   for t in thread_list:  # 等待所有线程执行完毕
      t.join()

   print('final num:', num)
8.线程锁与GIL之间的关系?
   加入GIL主要的原因是为了降低程序的开发的复杂度,
   比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,
   每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序里的线程和 py解释器自己的线程是并发运行的,
   假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,
   可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,
   为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动
9.递归锁(Rlock 不用递归锁而多重加lock锁会导致被锁住,程序卡死)
   import threading, time


   def run1():
      print("grab the first part data")
      lock.acquire()
      global num
      num += 1
      lock.release()
      return num


   def run2():
      print("grab the second part data")
      lock.acquire()
      global num2
      num2 += 1
      lock.release()
      return num2


   def run3():
      lock.acquire()
      res = run1()
      print('--------between run1 and run2-----')
      res2 = run2()
      lock.release()
      print(res, res2)


   if __name__ == '__main__':

      num, num2 = 0, 0
      lock = threading.RLock() #注意递归锁是Rlock
      for i in range(10):
         t = threading.Thread(target=run3)
         t.start()

   while threading.active_count() != 1:
      print(threading.active_count())
   else:
      print('----all threads done---')
      print(num, num2)
10.Semaphore(信号量):
   同时允许一定数量的线程更改数据
   import threading, time

   def run(n):
      semaphore.acquire()
      time.sleep(1)
      print("run the thread: %s\n" % n)
      semaphore.release()

   if __name__ == '__main__':

      num = 0
      semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
      for i in range(20):
         t = threading.Thread(target=run, args=(i,))
         t.start()

   while threading.active_count() != 1:
      pass  # print threading.active_count()
   else:
      print('----all threads done---')
      print(num)
11.Events事件:通过Event来实现两个或多个线程间的交互
   event = threading.Event()
   # a client thread can wait for the flag to be set
   event.wait()
   # a server thread can set or reset it
   event.set()
   event.clear()
   一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则:
   import threading,time
   import random
   def light():
       if not event.isSet():
           event.set() #wait就不阻塞 #绿灯状态
       count = 0
       while True:
           if count < 10:
               print('\033[42;1m--green light on---\033[0m')
           elif count <13:
               print('\033[43;1m--yellow light on---\033[0m')
           elif count <20:
               if event.isSet():
                   event.clear()
               print('\033[41;1m--red light on---\033[0m')
           else:
               count = 0
               event.set() #打开绿灯
           time.sleep(1)
           count +=1
   def car(n):
       while 1:
           time.sleep(random.randrange(10))
           if  event.isSet(): #绿灯
               print("car [%s] is running.." % n)
           else:
               print("car [%s] is waiting for the red light.." %n)
   if __name__ == '__main__':
       event = threading.Event()
       Light = threading.Thread(target=light)
       Light.start()
       for i in range(3):
           t = threading.Thread(target=car,args=(i,))
           t.start()
12.python queue队列(线程队列)
   class queue.Queue(maxsize=0) #先入先出
   class queue.LifoQueue(maxsize=0) #last in fisrt out
   class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
   Queue.qsize()
   Queue.empty() #return True if empty
   Queue.full() # return True if full
   Queue.put(item, block=True, timeout=None) #将项目放入队列中。如果可选的args块是true,则超时为None(缺省值),如果需要则阻塞,直到空闲槽可用。如果超时是一个正数,它会在大多数超时秒中阻塞,如果在那个时间内没有空闲槽,则会引发完全的异常。否则(块是false),如果一个空闲槽立即可用,则在队列上放置一个项,否则就会抛出完全异常(在这种情况下会忽略超时)   Queue.put_nowait(item) #Equivalent to put(item, False).
   Queue.get(block=True, timeout=None) #从队列中删除并返回一个项目。如果可选的args块是true,则超时为None(缺省值),如果需要则阻塞,直到有可用的项。如果超时是一个正数,它会在大多数超时秒中阻塞,如果在那个时间内没有可用的项,则会抛出空的异常。否则(块是false),如果立即可用,返回一个项目,否则将抛出空异常(在这种情况下忽略超时)   Queue.get_nowait() #Equivalent to get(False).
   两种方法来支持跟踪队列的任务是否已经被守护进程的消费者线程完全地处理。
   Queue.task_done()
   Queue.join() block直到queue被消费完毕
13.生产者消费者模型
   生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。
   生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,
   所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,
   消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
   import threading
   import queue


   def producer():
      for i in range(10):
         q.put("骨头 %s" % i)

      print("开始等待所有的骨头被取走...")
      q.join()
      print("所有的骨头被取完了...")


   def consumer(n):
      while q.qsize() > 0:
         print("%s 取到" % n, q.get())
         q.task_done()  # 告知这个任务执行完了


   q = queue.Queue()

   p = threading.Thread(target=producer, )
   p.start()

   c1 = consumer("李闯")


   import time,random
   import queue,threading
   q = queue.Queue()
   def Producer(name):
     count = 0
     while count <20:
       time.sleep(random.randrange(3))
       q.put(count)
       print('Producer %s has produced %s baozi..' %(name, count))
       count +=1
   def Consumer(name):
     count = 0
     while count <20:
       time.sleep(random.randrange(4))
       if not q.empty():
           data = q.get()
           print(data)
           print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
       else:
           print("-----no baozi anymore----")
       count +=1
   p1 = threading.Thread(target=Producer, args=('A',))
   c1 = threading.Thread(target=Consumer, args=('B',))
   p1.start()
   c1.start()
14.多进程模块multiprocessing
   from multiprocessing import Process
   import time


   def f(name):
      time.sleep(2)
      print('hello', name)


   if __name__ == '__main__':
      p = Process(target=f, args=('bob',))
      p.start()
      p.join()
   14.1展示进程号:
   from multiprocessing import Process
   import os


   def info(title):
      print(title)
      print('module name:', __name__)
      print('parent process:', os.getppid())
      print('process id:', os.getpid())
      print("\n\n")


   def f(name):
      info('\033[31;1mfunction f\033[0m')
      print('hello', name)


   if __name__ == '__main__':
      info('\033[32;1mmain process line\033[0m')
      p = Process(target=f, args=('bob',))
      p.start()
      p.join()
   14.2进程间通讯
      14.2.1Queues方法
         from multiprocessing import Process, Queue


         def f(q):
            q.put([42, None, 'hello'])


         if __name__ == '__main__':
            q = Queue()
            p = Process(target=f, args=(q,))
            p.start()
            print(q.get())  # prints "[42, None, 'hello']"
            p.join()
      14.2.2Pipes方法
         from multiprocessing import Process, Pipe


         def f(conn):
            conn.send([42, None, 'hello'])
            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']"
            p.join()
      14.2.3Managers方法
         from multiprocessing import Process, Manager


         def f(d, l):
            d[1] = '1'
            d['2'] = 2
            d[0.25] = None
            l.append(1)
            print(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))
                  p.start()
                  p_list.append(p)
               for res in p_list:
                  res.join()

               print(d)
               print(l)
   14.3进程同步
      from multiprocessing import Process, Lock


      def f(l, i):
         l.acquire()
         try:
            print('hello world', i)
         finally:
            l.release()


      if __name__ == '__main__':
         lock = Lock()

         for num in range(10):
            Process(target=f, args=(lock, num)).start()
15.进程池
   进程池中有两个方法:
   apply;
   apply_async;
   from  multiprocessing import Process, Pool
   import time
   def Foo(i):
      time.sleep(2)
      return i + 100
   def Bar(arg):
      print('-->exec done:', arg)
   pool = Pool(5)
   for i in range(10):
      pool.apply_async(func=Foo, args=(i,), callback=Bar)
   # pool.apply(func=Foo, args=(i,))
   print('end')
   pool.close()
   pool.join()  # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。


16.协程:
   协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
   协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
   协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
   协程的好处:
      无需线程上下文切换的开销
      无需原子操作锁定及同步的开销
      原子操作(atomic operation)是不需要同步的,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
      原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
      方便切换控制流,简化编程模型
      高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理
   缺点:
      无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
      进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
   16.1利用yield实现伪协程
      import time
      import queue


      def consumer(name):
         print("--->starting eating baozi...")
         while True:
            new_baozi = yield
            print("[%s] is eating baozi %s" % (name, new_baozi))
         # time.sleep(1)


      def producer():
         r = con.__next__()
         r = con2.__next__()
         n = 0
         while n < 5:
            n += 1
            con.send(n)
            con2.send(n)
            print("\033[32;1m[producer]\033[0m is making baozi %s" % n)


      if __name__ == '__main__':
         con = consumer("c1")
         con2 = consumer("c2")
         p = producer()
   16.2协程的特点
      必须在只有一个单线程里实现并发
      修改共享数据不需加锁
      用户程序里自己保存多个控制流的上下文栈
      一个协程遇到IO操作自动切换到其它协程
   16.3Greenlet实现协程(手动)
      # -*- coding:utf-8 -*-
      from greenlet import greenlet
      def test1():
         print(12)
         gr2.switch()
         print(34)
         gr2.switch()
      def test2():
         print(56)
         gr1.switch()
         print(78)
      gr1 = greenlet(test1)
      gr2 = greenlet(test2)
      gr1.switch()
   16.4Gevent 协程自动切换
      import gevent


      def func1():
         print('\033[31;1m李闯在跟海涛搞...\033[0m')
         gevent.sleep(2)
         print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m')


      def func2():
         print('\033[32;1m李闯切换到了跟海龙搞...\033[0m')
         gevent.sleep(1)
         print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m')


      gevent.joinall([
         gevent.spawn(func1),
         gevent.spawn(func2),
         # gevent.spawn(func3),
      ])
   16.5 比较同步与异步的性能差别
      from gevent import monkey;

      monkey.patch_all()
      import gevent
      from  urllib.request import urlopen


      def f(url):
         print('GET: %s' % url)
         resp = urlopen(url)
         data = resp.read()
         print('%d bytes received from %s.' % (len(data), url))


      gevent.joinall([
         gevent.spawn(f, 'https://www.python.org/'),
         gevent.spawn(f, 'https://www.yahoo.com/'),
         gevent.spawn(f, 'https://github.com/'),
      ])
17.通过gevent实现单线程下的多socket并发
   17.1server side :
      import sys
      import socket
      import time
      import gevent

      from gevent import socket, monkey

      monkey.patch_all()


      def server(port):
         s = socket.socket()
         s.bind(('0.0.0.0', port))
         s.listen(500)
         while True:
            cli, addr = s.accept()
            gevent.spawn(handle_request, cli)


      def handle_request(conn):
         try:
            while True:
               data = conn.recv(1024)
               print("recv:", data)
               conn.send(data)
               if not data:
                  conn.shutdown(socket.SHUT_WR)

         except Exception as  ex:
            print(ex)
         finally:
            conn.close()


      if __name__ == '__main__':
         server(8001)
   17.2client side :
      import socket

      HOST = 'localhost'  # The remote host
      PORT = 8001  # The same port as used by the server
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.connect((HOST, PORT))
      while True:
         msg = bytes(input(">>:"), encoding="utf8")
         s.sendall(msg)
         data = s.recv(1024)
         # print(data)

         print('Received', repr(data))
      s.close()
18.事件驱动与异步IO
   方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:
   1. CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?
   2. 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;
   3. 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;
   所以,该方式是非常不好的。

   方式二:就是事件驱动模型
   目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
   1. 有一个事件(消息)队列;
   2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
   3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()onKeyDown()等;
   4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

   当我们面对如下的环境时,事件驱动模型通常是一个好的选择:
   1.程序中有许多任务,而且2.任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且3.在等待事件到来时,某些任务会阻塞。
   4.当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。
   网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。
19.select多并发socket
   #_*_coding:utf-8_*_
   __author__ = 'Alex Li'

   import select
   import socket
   import sys
   import queue


   server = socket.socket()
   server.setblocking(0)

   server_addr = ('localhost',10000)

   print('starting up on %s port %s' % server_addr)
   server.bind(server_addr)

   server.listen(5)


   inputs = [server, ] #自己也要监测呀,因为server本身也是个fd
   outputs = []

   message_queues = {}

   while True:
       print("waiting for next event...")

       readable, writeable, exeptional = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里

       for s in readable: #每个s就是一个socket

           if s is server: #别忘记,上面我们server自己也当做一个fd放在了inputs列表里,传给了select,如果这个sserver,代表server这个fd就绪了,
               #就是有活动了, 什么情况下它才有活动? 当然 是有新连接进来的时候 呀
               #新连接进来了,接受这个连接
               conn, client_addr = s.accept()
               print("new connection from",client_addr)
               conn.setblocking(0)
               inputs.append(conn) #为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据, 把它放到inputs, 下一次loop,这个新连接
               #就会被交给select去监听,如果这个连接的客户端发来了数据 ,那这个连接的fdserver端就会变成就续的,select就会把这个连接返回,返回到
               #readable 列表里,然后你就可以loop readable列表,取出这个连接,开始接收数据了, 下面就是这么干 的

               message_queues[conn] = queue.Queue() #接收到客户端的数据后,不立刻返回 ,暂存在队列里,以后发送

           else: #s不是server的话,那就只能是一个 与客户端建立的连接的fd               #客户端的数据过来了,在这接收
               data = s.recv(1024)
               if data:
                   print("收到来自[%s]的数据:" % s.getpeername()[0], data)
                   message_queues[s].put(data) #收到的数据先放到queue,一会返回给客户端
                   if s not  in outputs:
                       outputs.append(s) #为了不影响处理与其它客户端的连接 , 这里不立刻返回数据给客户端


               else:#如果收不到data代表什么呢? 代表客户端断开了呀
                   print("客户端断开了",s)

                   if s in outputs:
                       outputs.remove(s) #清理已断开的连接

                   inputs.remove(s) #清理已断开的连接

                   del message_queues[s] ##清理已断开的连接


       for s in writeable:
           try :
               next_msg = message_queues[s].get_nowait()

           except queue.Empty:
               print("client [%s]" %s.getpeername()[0], "queue is empty..")
               outputs.remove(s)

           else:
               print("sending msg to [%s]"%s.getpeername()[0], next_msg)
               s.send(next_msg.upper())


       for s in exeptional:
           print("handling exception for ",s.getpeername())
           inputs.remove(s)
           if s in outputs:
               outputs.remove(s)
           s.close()

           del message_queues[s]

   #_*_coding:utf-8_*_
   __author__ = 'Alex Li'


   import socket
   import sys

   messages = [ b'This is the message. ',
                b'It will be sent ',
                b'in parts.',
                ]
   server_address = ('localhost', 10000)

   # Create a TCP/IP socket
   socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
             socket.socket(socket.AF_INET, socket.SOCK_STREAM),
             ]

   # Connect the socket to the port where the server is listening
   print('connecting to %s port %s' % server_address)
   for s in socks:
       s.connect(server_address)

   for message in messages:

       # Send messages on both sockets
       for s in socks:
           print('%s: sending "%s"' % (s.getsockname(), message) )
           s.send(message)

       # Read responses on both sockets
       for s in socks:
           data = s.recv(1024)
           print( '%s: received "%s"' % (s.getsockname(), data) )
           if not data:
               print(sys.stderr, 'closing socket', s.getsockname() )

20.selectors模块
   import selectors
   import socket

   sel = selectors.DefaultSelector()


   def accept(sock, mask):
      conn, addr = sock.accept()  # Should be ready
      print('accepted', conn, 'from', addr)
      conn.setblocking(False)
      sel.register(conn, selectors.EVENT_READ, read)


   def read(conn, mask):
      data = conn.recv(1000)  # Should be ready
      if data:
         print('echoing', repr(data), 'to', conn)
         conn.send(data)  # Hope it won't block
      else:
         print('closing', conn)
         sel.unregister(conn)
         conn.close()


   sock = socket.socket()
   sock.bind(('localhost', 10000))
   sock.listen(100)
   sock.setblocking(False)
   sel.register(sock, selectors.EVENT_READ, accept)

   while True:
      events = sel.select()
      for key, mask in events:
         callback = key.data
         callback(key.fileobj, mask)

21.RabbitMQ队列
   安装 http://www.rabbitmq.com/install-standalone-mac.html
   安装python rabbitMQ module
   pip install pika
   or
   easy_install pika
   or
   源码
   https://pypi.python.org/pypi/pika

   21.1send   # !/usr/bin/env python
   import pika

   connection = pika.BlockingConnection(pika.ConnectionParameters(
      'localhost'))
   channel = connection.channel()

   # 声明queue
   channel.queue_declare(queue='hello')

   # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
   channel.basic_publish(exchange='',
                         routing_key='hello',
                         body='Hello World!')
   print(" [x] Sent 'Hello World!'")
   connection.close()

   21.2receive   # _*_coding:utf-8_*_
   __author__ = 'Alex Li'
   import pika

   connection = pika.BlockingConnection(pika.ConnectionParameters(
      'localhost'))
   channel = connection.channel()

   # You may ask why we declare the queue again ‒ we have already declared it in our previous code.
   # We could avoid that if we were sure that the queue already exists. For example if send.py program
   # was run before. But we're not yet sure which program to run first. In such cases it's a good
   # practice to repeat declaring the queue in both programs.
   channel.queue_declare(queue='hello')


   def callback(ch, method, properties, body):
      print(" [x] Received %r" % body)


   channel.basic_consume(callback,
                         queue='hello',
                         no_ack=True)

   print(' [*] Waiting for messages. To exit press CTRL+C')
   channel.start_consuming()

   21.3 Work Queues
      21.3.1消息提供者代码
         import pika
         import time

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            'localhost'))
         channel = connection.channel()

         # 声明queue
         channel.queue_declare(queue='task_queue')

         # n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
         import sys

         message = ' '.join(sys.argv[1:]) or "Hello World! %s" % time.time()
         channel.basic_publish(exchange='',
                               routing_key='task_queue',
                               body=message,
                               properties=pika.BasicProperties(
                                  delivery_mode=2,  # make message persistent
                               )
                               )
         print(" [x] Sent %r" % message)
         connection.close()
      21.3.2消费者代码
         # _*_coding:utf-8_*_

         import pika, time

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            'localhost'))
         channel = connection.channel()


         def callback(ch, method, properties, body):
            print(" [x] Received %r" % body)
            time.sleep(20)
            print(" [x] Done")
            print("method.delivery_tag", method.delivery_tag)
            ch.basic_ack(delivery_tag=method.delivery_tag)


         channel.basic_consume(callback,
                               queue='task_queue',
                               no_ack=True
                               )

         print(' [*] Waiting for messages. To exit press CTRL+C')
         channel.start_consuming()
      21.3.3消息持久化 
         First, we need to make sure that RabbitMQ will never lose our queue. In order to do so, we need to declare it as durable:
         --channel.queue_declare(queue='hello', durable=True)
         Although this command is correct by itself, it won't work in our setup. That's because we've already defined a queue called hello which is not durable. RabbitMQ doesn't allow you to redefine an existing queue with different parameters and will return an error to any program that tries to do that. But there is a quick workaround - let's declare a queue with different name, for exampletask_queue:
         --channel.queue_declare(queue='task_queue', durable=True)
         This queue_declare change needs to be applied to both the producer and consumer code.
         At that point we're sure that the task_queue queue won't be lost even if RabbitMQ restarts. Now we need to mark our messages as persistent - by supplying a delivery_mode property with a value 2.
         --channel.basic_publish(exchange='',
                               routing_key="task_queue",
                               body=message,
                               properties=pika.BasicProperties(
                                  delivery_mode=2,  # make message persistent
                               ))
      21.3.4消息公平分发
         channel.basic_qos(prefetch_count=1)

      21.3.5带消息持久化+公平分发的完整代码
         生产者端
         # !/usr/bin/env python
         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.queue_declare(queue='task_queue', durable=True)

         message = ' '.join(sys.argv[1:]) or "Hello World!"
         channel.basic_publish(exchange='',
                               routing_key='task_queue',
                               body=message,
                               properties=pika.BasicProperties(
                                  delivery_mode=2,  # make message persistent
                               ))
         print(" [x] Sent %r" % message)
         connection.close()
         消费者端
         # !/usr/bin/env python
         import pika
         import time

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.queue_declare(queue='task_queue', durable=True)
         print(' [*] Waiting for messages. To exit press CTRL+C')


         def callback(ch, method, properties, body):
            print(" [x] Received %r" % body)
            time.sleep(body.count(b'.'))
            print(" [x] Done")
            ch.basic_ack(delivery_tag=method.delivery_tag)


         channel.basic_qos(prefetch_count=1)
         channel.basic_consume(callback,
                               queue='task_queue')

         channel.start_consuming()
      21.3.5Publish\Subscribe(消息发布\订阅) 
         之前的例子都基本都是11的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了,

         An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.

         Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息


         fanout: 所有bind到此exchangequeue都可以接收消息
         direct: 通过routingKeyexchange决定的那个唯一的queue可以接收消息
         topic:所有符合routingKey(此时可以是一个表达式)routingKeybindqueue可以接收消息

            表达式符号说明:#代表一个或多个字符,*代表任何字符
               例:#.a会匹配a.aaa.aaaa.a                   *.a会匹配a.ab.ac.a              注:使用RoutingKey#Exchange Typetopic的时候相当于使用fanout 

         headers: 通过headers 来决定把消息发给哪些queue

      21.3.6消息publisher

         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='logs',
                                  type='fanout')

         message = ' '.join(sys.argv[1:]) or "info: Hello World!"
         channel.basic_publish(exchange='logs',
                               routing_key='',
                               body=message)
         print(" [x] Sent %r" % message)
         connection.close()

      21.3.7消息subscriber
         # _*_coding:utf-8_*_
         __author__ = 'Alex Li'
         import pika

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='logs',
                                  type='fanout')

         result = channel.queue_declare(exclusive=True)  # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
         queue_name = result.method.queue

         channel.queue_bind(exchange='logs',
                            queue=queue_name)

         print(' [*] Waiting for logs. To exit press CTRL+C')


         def callback(ch, method, properties, body):
            print(" [x] %r" % body)


         channel.basic_consume(callback,
                               queue=queue_name,
                               no_ack=True)

         channel.start_consuming()
      21.3.8有选择的接收消息(exchange type=direct) 
         RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchangeexchange根据 关键字 判定应该将数据发送至指定队列。
         publisher:
         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='direct_logs',
                                  type='direct')

         severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
         message = ' '.join(sys.argv[2:]) or 'Hello World!'
         channel.basic_publish(exchange='direct_logs',
                               routing_key=severity,
                               body=message)
         print(" [x] Sent %r:%r" % (severity, message))
         connection.close()

         subscriber :
         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='direct_logs',
                                  type='direct')

         result = channel.queue_declare(exclusive=True)
         queue_name = result.method.queue

         severities = sys.argv[1:]
         if not severities:
            sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
            sys.exit(1)

         for severity in severities:
            channel.queue_bind(exchange='direct_logs',
                               queue=queue_name,
                               routing_key=severity)

         print(' [*] Waiting for logs. To exit press CTRL+C')


         def callback(ch, method, properties, body):
            print(" [x] %r:%r" % (method.routing_key, body))


         channel.basic_consume(callback,
                               queue=queue_name,
                               no_ack=True)

      21.3.9 更细致的消息过滤
         Although using the direct exchange improved our system, it still has limitations - it can't do routing based on multiple criteria.
         In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog unix tool, which routes logs based on both severity (info/warn/crit...) and facility (auth/cron/kern...).
         That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'.

         publisher:
         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='topic_logs',
                                  type='topic')

         routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
         message = ' '.join(sys.argv[2:]) or 'Hello World!'
         channel.basic_publish(exchange='topic_logs',
                               routing_key=routing_key,
                               body=message)
         print(" [x] Sent %r:%r" % (routing_key, message))
         connection.close()

         ------------------------------------------------------------------
         subscriber:
         import pika
         import sys

         connection = pika.BlockingConnection(pika.ConnectionParameters(
            host='localhost'))
         channel = connection.channel()

         channel.exchange_declare(exchange='topic_logs',
                                  type='topic')

         result = channel.queue_declare(exclusive=True)
         queue_name = result.method.queue

         binding_keys = sys.argv[1:]
         if not binding_keys:
            sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
            sys.exit(1)

         for binding_key in binding_keys:
            channel.queue_bind(exchange='topic_logs',
                               queue=queue_name,
                               routing_key=binding_key)

         print(' [*] Waiting for logs. To exit press CTRL+C')


         def callback(ch, method, properties, body):
            print(" [x] %r:%r" % (method.routing_key, body))


         channel.basic_consume(callback,
                               queue=queue_name,
                               no_ack=True)

         channel.start_consuming()

         ---------------------------------------------------------------
         To receive all the logs run:

         python receive_logs_topic.py "#"
         To receive all logs from the facility "kern":

         python receive_logs_topic.py "kern.*"
         Or if you want to hear only about "critical" logs:

         python receive_logs_topic.py "*.critical"
         You can create multiple bindings:

         python receive_logs_topic.py "kern.*" "*.critical"
         And to emit a log with a routing key "kern.critical" type:

         python emit_log_topic.py "kern.critical" "A critical kernel error"

你可能感兴趣的:(Python)