No.34生产者消费者模型

No.34

今日概要

  • 生产者消费者模型
  • JoinableQueue
  • 进程之间的数据共享
  • 初识线程
  • threading 模块
  • 测试

内容回顾

  • tcp协议:

    • 可靠:被拦截会重发,不会丢数据。
    • 不安全:被拦截的数据别人能看懂。
  • 迭代器:

    • 只能取一次值

      • for
      • list
      • __next__
    • 不取值不执行

      ret = filter(lambda n:n%3==0, range(10))
      
      # ret是filter对象,有__iter__方法,也有__next__方法。
      # 既是可迭代对象,也是迭代器。
      print(ret)
      
      # list(ret) 通过强制转换取迭代器中的值 [0, 3, 6, 9]
      print(len(list(ret)))
      结果:4
      
      # 迭代器中的值被取一次之后就没有了 []
      print(list(ret))
      结果:0
      
      def demo():
          for i in range(4):
              yield i
      
      g = demo()  # g是生成器函数也是特殊的迭代器
      
      g1 = (i for i in g)  # g1是生成器
      g2 = (i for i in g1) # g2是生成器
      
      print(list(g1)) # 强制转换取值
      结果:[0,1,2,3]
      
      print(list(g2)) # g1值被取走一次后就没有了
      结果:[ ]
      
      def add(n, i):
          return n+i
      
      def test():
          for i in range(4):
              yield i
              
      g = test() # 生成器
      for n in [1, 10]: # for循环给g赋了两次值
          g=(add(n,i) for i in g) # g是生成器
      '''
      n=1
      g=(add(1,i) for i in test())
      n=10
      g=(add(10,i) for i in (add(10,i) for i in test))
      '''
      print(list(g))
      结果:
      [20, 21, 22, 23]
      
  • IO操作

    • I → input 向内存输入
      • input / read / recv / recvfrom / accept / connect / close
    • O → ouptut 从内存输出
      • print / write / send / sendto / accept / connect / close
  • start / terminate / join

    • start / terminate: 异步非阻塞
    • join:同步阻塞
  • IPC

    • 进程之间通信
    • 内置ipc机制:
      • 队列
      • 管道
    • 第三方工具提供的ipc机制:
      • redic
      • memcache
      • kafka
      • rabbitmq

内容详细

1.生产者消费者模型

解耦:把写在一起的大功能分开写成多个小功能。

import time
import random
from multiprocessing import Process, Queue

def producer(q, name, food):
    for i in range(1, 11):
        time.sleep(random.random())
        fd = '%s%s' % (food, i)
        q.put(fd)
        print('%s生产了一个%s' % (name, food))

def consumer(q, name):
    while True:
        food = q.get()
        if not food:
            q.put(None)
            break
        time.sleep(random.randint(1, 3))
        print('%s吃了%s' % (name, food))

def pc(p_count, c_count):
    q = Queue(10)
    p_lst = []
    for i in range(p_count):
        p = Process(target=producer, args=(q, 'alex', '猪蹄'))
        p.start()
        p_lst.append(p)
    for i in range(c_count):
        c = Process(target=consumer, args=(q, 'wusir'))
        c.start()
    for p in p_lst:
        p.join()
    for i in range(c_count):
        q.put(None)

if __name__ == '__main__':
    pc(2,3)

2.Joinablequeue

import time
import random
from multiprocessing import Process, JoinableQueue

def producer(jq, name, food):
    for i in range(1, 11):
        time.sleep(random.random())
        fd = '%s%s' % (food, i)
        jq.put(fd) # 每次put一次数据之后,序列进行count+1计数
        print('%s生产了一个%s' % (name, food))
	jq.join() # put的数据全部消费完后才结束阻塞

def consumer(jq, name):
    while True:
        food = jq.get()
        time.sleep(random.randint(1, 3))
        print('%s吃了%s' % (name, food))
        jq.task_done() # 每次get到的数据处理结束后,序列进行count-1计数

if __name__ == '__main__':
    jq = JoinableQueue()
    p = Process(target=producer, args=(jq, 'alex', '猪蹄'))
    p.start()
    c = Process(target=consumer, args=(jq, 'eric'))
    c.daemon = True
    c.start()
    p.join()

3.进程之间的数据共享

from multiprocessing import Manager, Process, Lock

def func(dic, lock):
    with lock:
        dic['count'] -= 1

if __name__ == '__main__':
    m = Manager()
    lock = Lock()  # 不加锁数据不安全,可能多个进程拿到同一个数剧同时修改。
    dic = m.dict({'count': 60})  # 多个进程共享的字典。
    lst = []
    for i in range(60):
        p = Process(target=func, args=(dic, lock))
        p.start()
        lst.append(p)
    for p in lst:
        p.join()
    print(dic)

# multiprocessing中有一个Manager类
# 封装了所有和进程相关的
# 数据共享和数据传递的对象
# 但是对于字典、列表这一类对象操作的时候会产生数据不安全问题
# 需要通过加锁解决,建议尽量少的使用这种方式

4.初识线程

线程和进程的区别

  • 线程:
    • 开销小、数据共享、最小的CPU调度单位。
    • 线程是进程的一部分。
  • 进程:
    • 开销大、数据隔离、最小的资源分配单位。

cpython解释器无法实现多线程并行(同时间点利用多核)

  • GIL 全局解释器锁

    • 保证了整个python程序中,只能有一个线程被CPU执行。
    • 加锁原因:cpython解释器中特殊的垃圾回收机制。
  • gc 垃圾回收机制

    • 专门由解释器的一条线程来完成垃圾回收机制,对每一个在程序中的变量统计引用计数。
    • 如果python程序中的其它线程和该线程并行,那么该线程就无法统计与之并行线程中变量的引用计数。
  • Python解释器

    • cpython
      • 有GIL锁,无法利用多核。
    • pypy
      • 有GIL锁,无法利用多核。
    • jpython 可以利用多核
    • ironpython 可以利用多核
  • 总结

    • 虽然GIL锁导致线程无法实现并行,但不影响高IO操作程序的效率,只会影响高计算操作程序的效率。
    • 遇到高计算操作解决方式
      • 多进程 + 多线程
      • 分布式

5.threading 模块

开启线程

import os
from threading import Thread
# multiprocessing 是完全仿照着 threading 写的

def func():
    print(os.getppid())

Thread(target=func,).start() # 开启线程的速度非常的快
print('--->',os.getppid())

结果:
8420 # 子线程先打印,由此可见开启线程的效率远高于开启进程。
---> 8420 
# 异步
import os
import time
from threading import Thread

def func():
    print('start son thread')
    time.sleep(1)
    print('end son thread',os.getppid())

Thread(target=func,).start()
print('start')
time.sleep(0.5)
print('end',os.getppid())

开启多个子线程

import os
import time
from threading import Thread

def func(i):
    print('start son thread', i)
    time.sleep(1)
    print('end son thread', i, os.getppid(),)

for i in range(10):
    Thread(target=func, args=(i,)).start()

join方法

主线程什么时候结束?

  • 等待所有子线程结束之后才结束。
  • 主线程如果结束了,主进程也就结束了。
import os
import time
from threading import Thread

def func(i):
    print('start son thread', i)
    time.sleep(1)
    print('end son thread', i, os.getppid(),)

t = Thread(target=func, args=(1,))
t.start()
t.join()  # 阻塞直到子线程执行结束才结束
print('子线程执行完毕')
import time
from threading import Thread

def func(i):
    print('start son thread', i)
    time.sleep(1)
    print('end son thread', i)
lst = []
for i in range(10):
    t = Thread(target=func, args=(i,))
    t.start()
    lst.append(t)
for t in lst:
    t.join()
print('子线程执行完毕')

面向对象的方式启动线程

import time
from threading import Thread

class MyThread(Thread):
    def __init__(self, i):
        self.i = i
        super().__init__()

    def run(self):
        print('start', self.i)
        time.sleep(1)
        print('end', self.i)

for i in range(10):
    MyThread(i).start()
import time
from threading import Thread

class MyThread(Thread):
    def __init__(self, i):
        self.i = i
        super().__init__()

    def run(self):
        print('start', self.ident) # 线程id
        time.sleep(1)
        print('end', self.i)

for i in range(10):
    t = MyThread(i)
    t.start()
    print(t.ident) # 线程id
# current_thread()在哪一个线程中调用,得到的结果就是当前线程的对象(信息)。
from threading import current_thread, Thread

def func():
    t = current_thread()  # current_thread()是一个线程对象,相当于面向对象方式开启线程中的self。
    print(t.ident)

t = Thread(target=func)
t.start()
print(current_thread().ident)  # 主线程id
import time
from threading import Thread,current_thread,enumerate,active_count

def func():
    t = current_thread()
    print(t.ident)
    time.sleep(1)

t = Thread(target=func)
t.start()
print(enumerate())  # 查看当前有多少线程对象,返回的是列表
print(active_count())  # 相当于len(enumerate())
结果:
17848
[<_MainThread(MainThread, started 16140)>, <Thread(Thread-1, started 17848)>]
2

补充:

  • 主进程可以通过terminate方法结束子进程
  • 主线程没有方法结束子线程

6测试

  • 进程和线程的效率差

    import time
    from multiprocessing import Process
    from threading import Thread
    
    def func(a,b):
        c = a+b
    
    if __name__ == '__main__':
        start = time.time()
        p_lst = []
        for i in range(100):
            p = Process(target=func, args=(i, i*2))
            p.start()
            p_lst.append(p)
        for p in p_lst:
            p.join()
        print('process',time.time()-start)
    
        start = time.time()
        t_lst = []
        for i in range(100):
            t = Thread(target=func, args=(i, i*2))
            t.start()
            t_lst.append(p)
        for t in t_lst:
            t.join()
        print('thread',time.time()-start)    
    结果:
    process 2.306135892868042
    thread 0.014492273330688477
    # 开一百个进程和一百个线程,效率差两百倍。
    # 一般情况开的进程个数为CPU个数的1到2倍。
    
  • 数据隔离还是共享

    from threading import Thread
    
    n = 100
    def func():
        global n # 不要在子线程里随便修改全局变量
        n -= 1
        
    lst = []
    for i in range(100):
        t = Thread(target=func)
        t.start()
        lst.append(t)
    for t in lst:
        t.join()
    
    print(n)
    结果:
    0
    
  • 守护线程

    import time
    from threading import Thread
    
    def son1():
        while True:
            print('in son1')
            time.sleep(0.5)
    
    def son2():
        for i in range(5):
            time.sleep(1)
    
    
    t = Thread(target=son1)
    t.daemon = True
    t.start()
    Thread(target=son2).start()
    time.sleep(3)
    
    # 守护线程一直等到所有的非守护线程代码都结束之后才结束,除了守护主线程也会守护子线程。
    # 守护进程只用等到主进程代码执行结束就结束。
    
posted @ 2020-04-07 21:26  Sco_Lunatic  阅读(86)  评论(0)    收藏  举报