python死锁、队列、生产者和消费者模式、协程

一、死锁

在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁现象。

如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套。

import threading
import time

def test1():
    l1.acquire()
    print('test1..')
    time.sleep(1)
    l2.acquire()
    print('test1...--->>>')
    l2.release()
    l1.release()


def test2():
    l2.acquire()
    print('test2...')
    l1.acquire()
    print('test2...--->>>')
    l1.release()
    l2.release()


l1 = threading.Lock()
l2 = threading.Lock()


def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    t1.start()
    t2.start()


if __name__ == '__main__':
    main()

运行结果:

二、线程队列

队列是一种先进先出(FIFO)的存储数据结构,就比如排队上厕所一个道理。

1.创建一个“队列”对象

import Queue # 导入模块

q = Queue.Queue(maxsize = 10)

Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

2.将一个值放入队列中 q.put(10)

调用队列对象的put()方法在队尾插入一个项目。

3.将一个值从队列中取出q.get()

从队头删除并返回一个项目。如果取不到数据则一直等待。

4.q.qsize() 返回队列的大小

5.q.empty() 如果队列为空,返回True,反之False

6.q.full() 如果队列满了,返回True,反之False

7.q.put_nowait(item) ,如果取不到不等待,之间抛出异常。

8.q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号

9.q.join() 收到q.task_done()信号后再往下执行,否则一直等待。或者最开始时没有放数据join()不会阻塞。

q.task_done() 和 q.join() 通常一起使用。

import queue

q = queue.Queue(3)  # 创建线程队列对象
q.put('嘿嘿嘿')
q.put(123)
q.put(['x', 'y'])
print(q.qsize())
# q.put('xxx')
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) 
# print(q.get_nowait())   #  报错  队列空了
print(q.qsize())


def fun():
    print('xxx')
    q.task_done()


fun()
q.join()
print('xxx')
'''
3
嘿嘿嘿
123
['x', 'y']
0
xxx
'''
import queue

q = queue.Queue(3)
q.put('xxx')
print(q.get())
q.task_done()
q.join()
print('over')
'''
xxx
over
'''

三、生产者与消费者模式

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

(1)什么是生产者消费者模式

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

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

import queue, threading


def product(name):
    count = 1
    while count <= 100:
        q.join()
        l.acquire()
        q.put(count)
        print('{}正在做第{}碗面条'.format(name, count))
        count += 1
        l.release()


def customer(name):
    count = 1
    while count <= 100:
        data = q.get()
        l.acquire()
        print('{}正在吃第{}碗面条'.format(name, data))
        count += 1
        l.release()
        q.task_done()


def main():
    t1 = threading.Thread(target=product, args=('大大娘',))
    t2 = threading.Thread(target=customer, args=('泼猴',))
    t1.start()
    t2.start()


l = threading.Lock()
q = queue.Queue()

if __name__ == '__main__':
    main()
'''
大大娘正在做第1碗面条
泼猴正在吃第1碗面条
大大娘正在做第2碗面条
泼猴正在吃第2碗面条
大大娘正在做第3碗面条
泼猴正在吃第3碗面条
大大娘正在做第4碗面条
泼猴正在吃第4碗面条
''' '''
'''

四、GIL全局解释锁

GIL 即 :global interpreter lock 全局解释所。

在进行GIL讲解之前,我们可以先了解一下并行和并发:

并行:多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。

并发:CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺.

并行和并发同属于多任务,目的是要提高CPU的使用效率。这里需要注意的是,一个CPU永远不可能实现并行,即一个CPU不能同时运行多个程序。

Guido van Rossum(吉多·范罗苏姆)创建python时就只考虑到单核cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了GIL这把超级大锁。因为cpython解析只允许拥有GIL全局解析器锁才能运行程序,这样就保证了保证同一个时刻只允许一个线程可以使用cpu。也就是说多线程并不是真正意义上的同时执行。

五、协程

协程:协助程序,线程和进程都是抢占式特点,线程和进程的切换我们是不能参与的。

而协程是非抢占式特点,协程也存在着切换,这种切换是由我们用户来控制的。

协程主解决的是IO的操作。

协程,又称微线程,纤程。英文名Coroutine

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

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

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

1、yield的简单实现

import time


def test1():
    while True:
        print('test1...')
        time.sleep(0.1)
        yield


def test2():
    while True:
        print('test2...')
        time.sleep(0.1)
        yield


def main():
    g1 = test1()
    g2 = test2()
    while True:
        next(g1)
        next(g2)


if __name__ == '__main__':
    main()
'''
test1...
test2...
test1...
test2...
''' '''
'''

 2、greenlet 模块

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

安装 :pip3 install greenlet。如果引入的时候还是报错,使用pycharm进行下载安装,

选择 file --> settings --> Project:项目名称 --> Project Interpreter-->…

greenlet模块

- gr1=greenlet(目标函数)

- gr1.switch() 切换执行

代码:

import greenlet


def test1():
    while True:
        print('test1')
        g2.switch()


def test2():
    while True:
        print('test2')
        g1.switch()



if __name__ == '__main__':
    g1 = greenlet.greenlet(test1)  # g1的执行目标
    g2 = greenlet.greenlet(test2)

    g1.switch()  # 让程序去g1指定的目标去执行
    print('程序结束了')
'''
test1
test2
test1
test2
test1
test2
''' '''
'''

 3、gevent模块

import gevent
import time
from gevent import monkey

# 打补丁
gevent.monkey.patch_all()


def test1():
    for i in range(5):
        print('test1...')
        time.sleep(0.5)


def test2():
    for i in range(5):
        print('test2...')
        time.sleep(0.5)


if __name__ == '__main__':
    # g1 = gevent.spawn(test1)
    # g2 = gevent.spawn(test2)
    gevent.joinall(
        [
            gevent.spawn(test1),
            gevent.spawn(test2)
        ]
    )
    # g1.join()
    # g2.join()
    print('程序结束...')
'''
test1...
test2...
test1...
test2...
test1...
test2...
test1...
test2...
test1...
test2...
程序结束...
'''

图片下载器

import requests
import gevent

def download_img(url, img_name):
    response = requests.get(url)
    with open('img/' + img_name, mode='wb') as f:
        f.write(response.content)


if __name__ == '__main__':
    url = 'https://b-ssl.duitang.com/uploads/item/201901/20/20190120133013_tbmhe.jpg'
    # download_img(url, 'wenzi.jpeg')
    url1 = 'https://b-ssl.duitang.com/uploads/item/201901/22/20190122163957_fPBnR.jpeg'
    # download_img(url1, 'cat.jpeg')

    gevent.joinall([gevent.spawn(download_img,url,'w.jpeg'),
                    gevent.spawn(download_img,url1,'m.jpeg')

        ])
    gevent.spawn(download_img)

运行结果:

 

 

猜你喜欢

转载自blog.csdn.net/weixin_42223833/article/details/86602019