第28天 协程 信号量 死锁现象与递归锁 事件event 定时器与线程queue gil解释器锁 多线程性能测试

协程

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

  1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
  2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点如下:

  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

  1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:
必须在只有一个单线程里实现并发
修改共享数据不需加锁
用户程序里自己保存多个控制流的上下文栈
附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

信号量

信号量代码演示(信号量指的是同一时间有多把锁并发运行)
Semaphore 导入信号量模块
from threading import Thread, Semaphore, current_thread
import time


def func():
    sm.acquire()  # 抢锁
    print("%s 干饭 " % current_thread().getName())
    time.sleep(3)
    sm.release()  # 释放锁


if __name__ == '__main__':
    sm = Semaphore(5)  # 一共有5把锁
    for i in range(21):
        t = Thread(target=func)
        t.start()

死锁现象与递归锁
出现死锁现象不会有报错提示,锁套锁会出现死锁的现象

模拟死锁代码演示
from threading import Thread, Lock

mutexA = Lock()
mutexB = Lock()


class Mythread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print("%s 抢到了A锁" % self.name)  # 线程1先抢到了a锁,继续抢了b锁。这时候其他三个线程都并发抢a锁,
                                         # 线程1释放了b锁,再释放了a锁,其他三个并发抢a锁的同时,线程1又抢了b锁并且睡了0.1秒
        mutexB.acquire()                 # 这个时候b锁被线程2抢到了,a锁在线程1的手里。线程1和线程2互相锁死。线程3和线程4还在等着抢a锁
        print("%s 抢到了B锁" % self.name)
        mutexB.release()

        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print("%s 抢到了B锁" % self.name)
        time.sleep(0.1)

        mutexA.acquire()
        print("%s 抢到了A锁" % self.name)
        mutexA.release()

        mutexB.release()


if __name__ == '__main__':
    t1 = Mythread("线程1")
    t2 = Mythread("线程2")
    t3 = Mythread("线程3")
    t4 = Mythread("线程4")

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    print("主线程")
死锁现象发生打印的结果:
线程1 抢到了A锁
线程1 抢到了B锁
线程1 抢到了B锁
线程2 抢到了A锁
主线程

递归锁

递归锁同一把锁可以连续acquire,以计数的方式来抢锁和释放锁
from threading import Thread, RLock

mutexA = mutexB = RLock()  # 递归锁,一个进程可以连续抢锁,抢一次锁技计数1次,第一次计数1,第二次计数2。。。。


# 其他的线程如果发现递归锁的计数不为0就直接堵塞在原地,等这把锁计数为0的时候才能抢锁

class Mythread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print("%s 抢到了A锁" % self.name)

        mutexB.acquire()
        print("%s 抢到了B锁" % self.name)
        mutexB.release()

        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print("%s 抢到了B锁" % self.name)
        time.sleep(0.1)

        mutexA.acquire()
        print("%s 抢到了A锁" % self.name)
        mutexA.release()

        mutexB.release()


if __name__ == '__main__':
    t1 = Mythread("线程1")
    t2 = Mythread("线程2")
    t3 = Mythread("线程3")
    t4 = Mythread("线程4")

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    print("主线程")

事件event
一个线程完毕,另外一个线程才开始运行,并不涉及到数据的交接,通过发送信号(数据)来提醒另外一个线程。

代码演示
from threading import Event, Thread, current_thread

e = Event()  # 默认全局变量为False


def f1():
    print("%s 运行" % current_thread().name)  # current_thread().name获取线程名字
    time.sleep(3)
    e.set()  # 把全局变量变成了True
    # e.clear()   # 把全局变量变成False
    # e.is_set()  # 判断有没有设置过,设置过就是True,没有设置过就是False

def f2():
    e.wait()  # 在原地等待,等到全局变量变为True。e.wait(n)可以设置等待时间,超时如果全局变量还没有变成True那么会自动执行代码
    print("%s 运行" % current_thread().name)


if __name__ == '__main__':
    t1 = Thread(target=f1)
    t2 = Thread(target=f2)

    t1.start()
    t2.start()

案例

from threading import Event, Thread, current_thread
import time
import random

e = Event()  # e = Ture


def task1():
    while True:
        e.clear()  # e = False
        print("红灯亮")
        time.sleep(2)

        e.set()  # e = True
        print("绿灯亮")
        time.sleep(3)
# task1:2秒亮一次红灯,三秒亮一次绿灯。不停的在循环

def task2():
    while True:
        if e.is_set():  # 判断e有没有被设置过,如果设置过就是True,没有设置过就是False
            print("%s 走你" % current_thread().name)
            break   # 线程只有进入“走你”才会结束循环
        else:
            print("%s 等灯" % current_thread().name)
            e.wait()  # 等待全局变量变为True


if __name__ == '__main__':
    Thread(target=task1).start()

    while True:
        time.sleep(random.randint(1, 5))  # 每1-5秒之间产生循环
        Thread(target=task2).start()

定时器与线程queue

定时器

from threading import Timer


def hello(x):
    print("hello,world", x)


t = Timer(0.2, hello, args=(10,))  # Timer(n,"xx")可以在定时器第一个逗号前面写上时间,表示过多长时间运行代码
t.start()

线程queue(里面有队列,堆栈,优先级队列)

import queue

队列(先进先出)
q = queue.Queue(3)
q.put(111)
q.put("aaa")
q.put((1, 2, 3))

print(q.get())
print(q.get())
print(q.get())

堆栈(后进先出)
q = queue.LifoQueue(3)
q.put(111)
q.put("aaa")
q.put((1, 2, 3))

print(q.get())
print(q.get())
print(q.get())

优先级队列(元组,第一个整型数字是优先级排序)
q = queue.PriorityQueue(3)
q.put((2, (111)))
q.put((3, "aaa"))
q.put((1, (1, 2, 3)))

print(q.get())
print(q.get())
print(q.get())

GIL解释器锁(cpython解释器自带的锁)
在python解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
python线程需要拿到解释器的gil锁才可以运行代码,相当于一个解释器执行权限,有了执行权限,操作系统才会给python代码分配cpu,一旦cpu被调走了,gil锁会直接释放
gil锁主要是为了保护python解释器内存管理的数据安全的,无法保护你自己写的python代码的数据安全
gil锁可以使线程并发,但是不能让线程并行(python运行一个进程只能用一个cpu,如果想要同时使用多个cpu来提升计算效率,需要开多个进程)

from threading import Thread, Lock
import time

mutex = Lock()
n = 100


def task():
    global n

    mutex.acquire()      # 互斥锁
    temp = n
    time.sleep(0.1)
    n = temp - 1
    mutex.release()


if __name__ == '__main__':
    l = []
    for i in range(100):
        t = Thread(target=task)
        l.append(t)
        t.start()

    for obj in l:
        obj.join()

    print(n)

多线程性能测试
纯计算的行为可以用多进程,使用多核优势来提升运行效率
纯io行为的可以在一个进程里面开多个线程,因为io行为多核优势就体现不出来了,
开多进程,需要开辟新的内存空间,把父进程的程序复制到子进程里面,开启进程。一旦产生io行为,操作系统会自动把cpu调走,io结束操作系统会把cup再调过来。
开多个线程,一个cpu来回切换,因为开线程的开销是远远小于开进程的开销的,所以效率也会高于多进程。

代码演示
from multiprocessing import Process
from threading import Thread
import os, time


def work():
    # res = 0
    # for i in range(100000000):
    #     res *= i
    time.sleep(5)


if __name__ == '__main__':
    l = []
    # print(os.cpu_count()) # 本机为8核
    start = time.time()
    for i in range(8):
        p = Process(target=work)   # 纯计算 多进程花费的时间12.8s    |纯io行为多进程运行时间为5.335s
        # p = Thread(target=work)     # 纯计算 多线程运行的时间46.45s    |纯io行为多线程运行时间为5.003s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print("run time is %s" % (stop - start))

猜你喜欢

转载自blog.csdn.net/Yosigo_/article/details/113064241
今日推荐