• python周报第十一周


    0.本周知识点预览

    •  调用线程的两种方式
    •  队列
    •  生产者消费者模型
    •  线程锁的概念
    •  初级版线程池
    •  进程
    •  进程池
    •  协程
    •  缓存

    1.调用线程的两种方式

    1.自定义子类继承的模式(罕见)

    ##调用线程方法一(非常见)
    import threading
    
    ###自定义一个线程类,继承线程父类
    class MyThread(threading.Thread):
    
        def __init__(self,func,args):
            self.func = func
            self.args = args
            ###不影响执行父类的__init__方法
            super(MyThread,self).__init__()
        
        ###因为CPU调用线程时都会执行线程对象的run方法,所以我们可以自定义run方法.
        def run(self):
    
            self.func(self.args)
    
    def f1(args):
        print(args)
    
    t = MyThread(f1,123)
    t.start()

    执行结果如下:

    123

    代码解析:这种方法不常见,只是说明下线程对象的调用方式,实际上是线程对象被调用时,CPU执行了其run方法,所以我们可以随意重写该方法。

    2.直接调用线程类(常见)

    ##调用线程方法二(常见)
    import threading
    
    def f2(args):
        print(args)
    
    ##直接实例化线程类 t
    = threading.Thread(target=f2,args=(123,)) t.start()

    执行结果如下:

    123

    代码解析:这种方式调用线程是最常见的,也是经常用的。

    2.队列

    1.先进先出队列

    import queue
    
    ###创建一个队列队形,最大容量为3(maxsize)
    q = queue.Queue(maxsize=3)
    ###队列中放一个值
    q.put(11)
    ###队列中放一个值,超时时间为2s
    q.put(22,timeout=2)
    ###队列中放一个值,假如队列容量满了,立刻报错,假如没有block=False的参数,会夯住.
    q.put(33,block=False)
    ###打印现在队列中元素的个数
    print("队列长度为:",q.qsize())
    ###取队列中的值
    print("第一次取的值:",q.get())
    ###这个task_done()和join()匹配,当join()时,主程序会等待所有元素都从队列中取出,且需要task_done来告诉程序已取出.
    q.task_done()
    ###从队列中取值,当设置block=False时,只要发现队列中没有元素,直接报错,默认是True,会夯住
    print("第二次取的值:",q.get(block=False))
    q.task_done()
    ###从队列中取值,假如设置timeout=2,程序执行2s后还没取到元素,直接报错
    print("第三次取的值:",q.get(timeout=2))
    q.task_done()
    q.join()

    执行结果如下:

    队列长度为: 3
    第一次取的值: 11
    第二次取的值: 22
    第三次取的值: 33

    代码解析:可以看到,普通队列是先进先出的原则。

    2.后进先出队列

    import queue
    
    ###创建一个后进先出队列
    q = queue.LifoQueue(4)
    ###put,get方法都和先进先出队列一样
    q.put(123,block=False)
    q.put(234,timeout=2)
    q.put(345)
    print("第一个取的元素:",q.get())
    print("第二个取的元素:",q.get())
    print("第三个取的元素:",q.get())

    执行结果如下:

    第一个取的元素: 345
    第二个取的元素: 234
    第三个取的元素: 123

    代码解析:后进先出和先进先出队列的put,get方法一样,定义上有些区别。

    3.优先级队列 

    import queue
    
    ###创建一个优先级队列
    q = queue.PriorityQueue()
    ###put的是一个元组,第一个元素是权重,第二个是元素,权重的数字越小优先级越高.假如两个以上权重相同,先取先put的元素
    q.put((1,123))
    q.put((0,234))
    q.put((0,235))
    q.put((4,555))
    
    print("第一个取的元素:",q.get())
    print("第二个取的元素:",q.get())
    print("第三个取的元素:",q.get())
    print("第四个取的元素:",q.get())

    执行结果如下:

    第一个取的元素: (0, 234)
    第二个取的元素: (0, 235)
    第三个取的元素: (1, 123)
    第四个取的元素: (4, 555)

    代码解析:优先级队列的方法与普通队列方法一样。

    4.双向队列

    import queue
    
    ###创建一个双向队列
    q = queue.deque()
    
    ### append 右边放
    ### appendlift 左边放
    q.append(123)
    q.append(234)
    q.append(345)
    q.appendleft(222)
    q.appendleft(111)
    
    ###生成队列: 111 222 123 234 345
    
    ###pop 右边取
    print(q.pop())
    print(q.pop())
    print(q.pop())
    print(q.pop())
    print(q.pop())
    
    ##poplift 左边取
    print(q.popleft())
    print(q.popleft())
    print(q.popleft())
    print(q.popleft())
    print(q.popleft())

    代码解析:双向队列的append、appendleft相当于普通队列的put方法,pop、popleft相当于get方法。

    3.生产者、消费者模型

    ##生产者消费者模型
    
    ###导入队列,线程,time用来模拟并发
    import queue
    import threading
    import time
    
    ###创建一个火车票的最大创建数
    q = queue.Queue(20)
    ###生产火车票
    def productor(arg):
        while True:
            q.put(arg + "火车票")
            ###生产火车票较快,每1s一次
            time.sleep(1)
    
    ###有两个售票员,并发售票
    for i in range(2):
        t1 = threading.Thread(target=productor,args=("售票员"+str(i)+"生产的 ",))
        t1.start()
    
    ###买票的程序
    def consumer(arg):
        while True:
            print(arg + q.get())
            time.sleep(5)
    
    ###12个人同时买票
    for i in range(12):
        t2 = threading.Thread(target=consumer,args=("客户"+str(i)+"消费 ",))
        t2.start()

    执行结果如下:

    客户0消费 售票员0生产的 火车票
    客户1消费 售票员1生产的 火车票
    客户2消费 售票员0生产的 火车票
    客户3消费 售票员1生产的 火车票
    客户4消费 售票员0生产的 火车票
    客户5消费 售票员1生产的 火车票
    客户6消费 售票员0生产的 火车票
    客户7消费 售票员1生产的 火车票
    客户8消费 售票员0生产的 火车票
    客户9消费 售票员1生产的 火车票
    客户10消费 售票员0生产的 火车票
    客户11消费 售票员1生产的 火车票
    客户0消费 售票员0生产的 火车票
    客户1消费 售票员1生产的 火车票
    客户2消费 售票员0生产的 火车票
    客户3消费 售票员1生产的 火车票
    客户4消费 售票员0生产的 火车票
    客户5消费 售票员1生产的 火车票
    客户6消费 售票员0生产的 火车票
    客户7消费 售票员1生产的 火车票
    客户8消费 售票员0生产的 火车票
    客户9消费 售票员1生产的 火车票
    客户10消费 售票员0生产的 火车票
    客户11消费 售票员1生产的 火车票
    客户0消费 售票员0生产的 火车票
    客户1消费 售票员1生产的 火车票

    代码解析:买票卖票只是一个例子,生活中有很多这样的例子,再比如说,去饭店买包子,厨师做包子,顾客吃包子。这样就会生成三种场景。

    场景一:顾客提出买包子,厨师在做包子,不买则不做。这样会导致顾客和厨师耦合度太高。

    场景二:厨师一直在做包子,顾客看到有包子则买,没有则等待,这样假如没有顾客,厨师做包子的数量会非常多。

    场景三:厨师一直在做包子,把做完的放入蒸笼内,蒸笼数量是固定的,顾客看到有包子则买,没有则等待。这样比较好。

    总结:我理解的生产者消费者模型,就是利用多线程和队列,一方在put,一方在get,双方共同维护一个数量有限的队列。

    4.线程锁 

    程序中出现多线程时,因为线程是共享数据的,所以假如出现多线程同时对同一个数据进行操作可能就会产生错误,例如下面的例子(无线程锁):

    同时打开10个线程对全局变量做自减操作:

    import threading
    import time
    
    NUM = 10
    
    lock = threading.RLock()
    
    def f1(l):
        global NUM
        NUM -=1
        time.sleep(1)
        print("结束:",NUM)
    
    for i in range(10):
        t = threading.Thread(target=f1,args=(lock,))
        t.start()

    执行结果如下:

    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0
    结束: 0

    代码解析:为什么结果都是0?因为同时开10个线程,在time.sleep(1)的时候都都已经对NUM进行了操作,导致NUM变为0。所以每个线程都输出0,这时就需要考虑对线程加锁,来保证每个线程的执行要等到上个线程执行完之后。

    1.单层普通线程锁

    import threading
    import time
    
    NUM = 10
    
    lock = threading.Lock()
    
    def f1(l):
        global NUM
        ###上锁
        l.acquire()
        NUM -= 1
    
        time.sleep(1)
        ###开锁
        l.release()
        print("结束:",NUM)
    
    for i in range(10):
        t = threading.Thread(target=f1,args=(lock,))
        t.start()

    执行结果如下:

    结束: 9
    结束: 8
    结束: 7
    结束: 6
    结束: 5
    结束: 4
    结束: 3
    结束: 2
    结束: 1
    结束: 0

    代码解析:调用threading.Lock()方法,生成一个线程锁对象,acquire()方法是加锁,release()方法是解锁。不过Lock()方法生成的锁只能锁一层。这种线程锁的特点就是每次都放行一个线程。

    2.双层普通线程锁

    import threading
    import time
    
    NUM = 10
    
    lock = threading.RLock()
    
    def f1(l):
        global NUM
        ###上锁
        l.acquire()
        NUM -= 1
        ###二次上锁
        l.acquire()
        time.sleep(1)
        ###开锁
        l.release()
        ###二次解锁
        l.release()
        print("结束:",NUM)
    
    for i in range(10):
        t = threading.Thread(target=f1,args=(lock,))
        t.start()

    执行结果如普通单层线程锁的例子,不过RLock()方法生成的锁可以用多层,所以,用普通线程锁多数用RLock(),这种线程锁的特点就是每次都放行一个线程。

    3.信号量线程锁

    import threading
    import time
    
    NUM = 10
    
    ###参数为每次放行的线程数
    lock = threading.BoundedSemaphore(3)
    
    def f1(l):
        global NUM
        ###上锁
        l.acquire()
        NUM -= 1
    
        time.sleep(1)
        ###开锁
        l.release()
        print("结束:",NUM)
    
    for i in range(10):
        t = threading.Thread(target=f1,args=(lock,))
        t.start()

    执行结果如下:

    结束: 7
    结束: 6
    结束: 6
    结束: 4
    结束: 4
    结束: 4
    结束: 1
    结束: 1
    结束: 1
    结束: 0

    代码解析:信号量的线程锁,特点是可以每次放行多个线程。

    4.事件线程锁

    import threading
    
    def func(i,e):
        print(i)
        e.wait()            ###类似检测是红绿灯的什么灯.如果红,则停,如果绿,则放行
        print(i+100)
    
    ###生成线程锁对象
    event = threading.Event()
    
    for i in range(5):
        t = threading.Thread(target=func,args=(i,event,))
        t.start()
    
    event.clear()           ###设置为红灯,全部阻塞
    inp = input(">>>")
    if inp == "1":
        event.set()         ###设置为绿灯,全部放行.

    执行结果如下:

    0
    1
    2
    3
    4
    >>>1
    100
    103
    101
    102
    104

    代码解析:事件线程锁的特点是,每次全部阻塞或全部放行线程,设置此线程锁时,需要先clear()方法全部阻塞,然后程序中的wait()方法会判断当前是阻塞状态,一直等到出现set()方法后,才会放行全部线程,否则就会一直阻塞。

    5.condition 线程锁

    ##线程锁  condition
    import threading
    
    ###创建线程锁
    con = threading.Condition()
    
    ###创建函数可以返回真假值
    def condition():
        ret = False
        r = input(">>> ")
        if r == "true":
            ret = True
        else:
            ret = False
        return ret
    
    
    ###目标函数
    def func(i,con):
        print(i)
        con.acquire()
        con.wait_for(condition)
        print(i+100)
        con.release()
    
    for i in range(5):
        t = threading.Thread(target=func,args=(i,con,))
        t.start()

    执行结果如下:

    0
    >>> 1
    2
    3
    4
    
    >>> false
    >>> true
    102
    >>> true
    103
    >>> 

    代码解析:得到一个线程锁之后,上锁解锁方法如之前的RLock(),不同的是,有个wait()方法,用来等待函数的返回值,而返回值是根据用户行为决定的,假如是true才会放行一个线程。

    6.定时任务模块Timer

    import threading
    import time
    
    def hello():
        print("hello world")
    
    print(time.ctime())
    t = threading.Timer(2,hello)
    t.start()
    t.join()
    print(time.ctime())

    执行结果如下:

    Tue Jul 19 23:50:47 2016
    hello world
    Tue Jul 19 23:50:49 2016

    代码解析:t = threading.Timer(2,hello),中的2代表延后的时间,hello代表延后时间所执行的函数。

    5.多进程与进程池

    1.创建多进程

    from multiprocessing import Process
    import time
    
    def f2(arg):
        time.sleep(1)
        print(arg)
    
    for i in range(10):
        p = Process(target=f2,args=(i,))
        p.start()

    执行结果如下:

    0
    1
    3
    2
    4
    5
    6
    7
    8
    9

    代码解析:多进程与多线程的使用方法基本一样,不同的就是使用场景,多进程适用于计算类程序,消耗CPU的场景,多线程适用于I/O频繁,不消耗CPU的场景。

    2.进程池

      在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,10几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了。
          Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。这里有一个简单的例子:

    from multiprocessing import Pool
    import time
    
    def f1(i):
        print(i)
        time.sleep(1)
    
    ###定义一个进程池,最大进程数为5.
    pool = Pool(processes=5)
    
    for i in range(20):
        # pool.apply(f1,args=(i,))
        pool.apply_async(f1,args=(i,))
    
    pool.close()
    pool.join()

    执行结果及现象:是每隔1s,会打印5个数字。这时用ps axu|grep python来看就会发现有5个进程了。

    代码分析:pool.apply_async()用来向进程池提交目标请求,pool.join()是用来等待进程池中的worker进程执行完毕,防止主进程在worker进程结束前结束。但是pool.join()必须使用在pool.close()或者pool.terminate()之后(源码写死的)。其中close()跟terminate()的区别在于close()会等待池中的worker进程执行结束再关闭pool,而terminate()则是直接关闭。

    6.协程基础

    协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

    最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

    第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

    from gevent import monkey;monkey.patch_all()
    import gevent
    import requests
    
    def f(url):
        print("GET: %s" % (url))
        resp = requests.get(url)
        data = resp.text
        print("%d bytes from %s " % (len(data),url))
    
    gevent.joinall([
        gevent.spawn(f,"https://www.python.org"),
        gevent.spawn(f,"https://www.baidu.com"),
        gevent.spawn(f,"https://github.com"),
    ])

    执行结果如下:

    GET: https://www.python.org
    GET: https://www.baidu.com
    GET: https://github.com
    227 bytes from https://www.baidu.com 
    47394 bytes from https://www.python.org 
    25529 bytes from https://github.com 

    代码分析:导入协程模块gevent,其实底层还是用的greenlet,这两个模块都要装。money.patch_all()是对网络socket的一个重写。gevent.spawn 的重要功能就是封装了greenlet里面的函数。初始化的greenlet放在了threads这个list里面,被传递给了 gevent.joinall 这个函数,它会阻塞当前的程序来执行所有的greenlet.

    7.缓存

    1.memcache

    #########memcache
    
    import memcache
    ###创建一个memcached连接对象
    mc = memcache.Client(["127.0.0.1:11211"],debug=True)
    ###set : 设置值
    mc.set("name","lk")
    ###geet: 取值
    ret = mc.get("name")
    print(ret)
    
    ###memcache 支持集群模式
    mc = memcache.Client([('127.0.0.1:11211', 1), ('127.0.0.1:11212', 3),],debug=True, cache_cas=True)
    
    mc.set("k1","v1")
    ret = mc.get("k1")
    print(ret)
    
    ###add 方法新增key / value ,假如之前有值,则报错
    mc.add("k3","v3")
    print(mc.get("k3"))
    
    ###replace  方法,替换key 的值,假如之前没值,则报错
    mc.replace("k3","haha")
    print(mc.get("k3"))
    
    ###set方法是设置一个key,set_multi 是设置多个key,以字典的形式.
    mc.set("k1","v11")
    mc.set_multi({"k2":"v22","k3":"v33"})
    print(mc.get("k2"))
    
    ###delete 方法删除key,假如没值,则报错
    mc.delete("k5")
    mc.delete("k1")
    print(mc.get("k1"))
    
    ###delete_multi 方法批量删除key,以字典的形式,假如没值,则报错.
    mc.delete_multi(["k1", "k2"])
    print(mc.get("k3"))
    
    ###get_multi 方法批量取值,得到字典的形式
    print(mc.get_multi(["k3", "k2"]))
    print(mc.get("k3"))
    
    ###append 方法,在key对应的value之后添加值,如:a -> 123 append("a",456)  a -> 123456
    mc.append("k3","haha")
    print(mc.get("k3"))
    print(mc.get("k2"))
    
    ###prepend 方法,在key对应的value之前添加值,如:a -> 123 append("a",456)  a -> 456123
    mc.prepend("k2","fuck")
    print(mc.get("k2"))
    
    ###incr 把key 的 value值自增
    mc.set("k6",100)
    mc.incr("k6",20)
    print(mc.get("k6"))
    
    ##decr 把key 的 value值自减
    mc.decr("k6",40)
    print(mc.get("k6"))
    
    ###gets / cas ,当多个终端同时对一个key操作时,很容易出现设置错误.这样就要用gets /cas
    ###gets 获取到值之后,会在本地缓存一个标记位,memcache服务端也会有一个标记位,假如在终端一取值之后,终端二也取值.
    ###终端一设置值是成功的,但是这时终端二也设置值,就会报错.
    #终端一:mc.gets("k6")
    #终端二:mc.gets("k6")
    #终端一:mc.cas("k6", 500) ->   设置值是成功的
    #终端二:mc.cas("k6",600)  ->  会报错,值已经发生了变化

    2.redis

    #########redis
    
    import redis
    
    ###生成一个redis 的连接对象
    r = redis.Redis(host="127.0.0.1", port=6379)
    
    r.set("name", "lk")
    print(str(r.get("name"),encoding="utf-8"))
    
    ###生成一个redis 连接池
    pool = redis.ConnectionPool(host="127.0.0.1", port=6379)
    r = redis.Redis(connection_pool=pool)
    r.set("foo", "Bar")
    print(r.get("foo"))
  • 相关阅读:
    敏捷软件开发实践-Code Review Process(转)
    随笔1
    随笔
    随笔
    低级错误
    随笔
    随笔2
    随笔
    这以前写的代码
    蛋疼的vs
  • 原文地址:https://www.cnblogs.com/Caesary/p/5681987.html
Copyright © 2020-2023  润新知