并发编程其它

内容概要

  一、死锁与递归锁(了解)

  二、信号量(了解)

  三、Event事件(了解)

  四、其它线程q(了解)

  五、进程池与线程池

  六、协程

  七、协程实现TCP并发(了解)

 

1、死锁与递归锁

  虽然互斥锁的语法看起来很简单,就是一acquire对应一release,但是在多并发情况下,使用多把互斥锁容易造成死锁现象

  死锁现象例子

from threading import Thread,Lock
import time

# metuxA = Lock()
# metuxB = Lock()  # 锁A和锁B是两把不同的锁

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        metuxA.acquire()
        print('{}抢到了锁A'.format(self.name))
        metuxB.acquire()
        print('{}抢到了锁B'.format(self.name))
        metuxA.release()
        metuxB.release()

    def func2(self):
        metuxB.acquire()
        print('{}抢到了锁B'.format(self.name))
        time.sleep(2)
        metuxA.acquire()
        print('{}抢到了锁A'.format(self.name))
        metuxA.release()
        metuxB.release()

if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()

  执行这个程序,会发生线程1抢了锁A,想抢锁B;但是线程2已经抢了锁B,它又想抢锁A,两个线程彼此卡住。

 

  要想解决死锁现象,可以将所有不同的锁设置为同一把递归锁

  Rlock是递归锁

from threading import Thread,RLock
import time

metuxA = metuxB = RLock()
# 递归锁的特点:
'''
递归锁可以被连续acquire和release,当每次acquire时,锁内部的计数器自动加一;
当每次release时,锁内部的计数器自动减一。当计数为0时,锁才能真正被释放,其它线程才能枪锁
'''

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        metuxA.acquire()
        print('{}抢到了锁A'.format(self.name))
        metuxB.acquire()
        print('{}抢到了锁B'.format(self.name))
        metuxA.release()
        metuxB.release()

    def func2(self):
        metuxB.acquire()
        print('{}抢到了锁B'.format(self.name))
        time.sleep(2)
        metuxA.acquire()
        print('{}抢到了锁A'.format(self.name))
        metuxA.release()
        metuxB.release()

if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()

 

2、信号量

  如果说Lock是多个人抢一把锁,那么信号量就是多个人可以抢多把锁

  Semaphore是信号量,Semaphore中可以设置参数,参数的值表示的是同一时间内能够被抢去使用的锁的数量

from threading import Thread,Semaphore
import time
import random

sm = Semaphore(5)

def work(i):
    sm.acquire()
    print('伞兵{}号准备就绪'.format(i))
    time.sleep(random.randrange(2,5))
    print('伞兵{}号落地成盒'.format(i))
    sm.release()

if __name__ == '__main__':
    for i in range(1,21):
        t = Thread(target=work,args=(i,))
        t.start()

 

3、Event事件

  一般来说,主线程会等待所有非守护线程结束之后才结束

  Event事件用于实现子线程之间彼此等待,或者子线程等待主线程

from threading import Thread, Event
import time

event = Event()

def traffic_light():
    print('红灯亮了')
    print(event.is_set())  # 打印_flag当前的值
    time.sleep(8)
    print('绿灯亮了')
    event.set()  # 设置_flag为True
    print(event.is_set())  # 打印_flag当前的值

def car(i):
    print('{}车在等待'.format(i))
    event.wait()  # 执行到这里,判断_flag的值,False时阻塞;True时同行
    print('{}车开了'.format(i))

if __name__ == '__main__':
    t2 = Thread(target=traffic_light)
    t2.start()
    for i in range(1,10):
        t = Thread(target=car,args=(i,))
        t.start()

event.clear()  # 设置_flag为False

 

4、其它线程q

# 堆栈队列
import queue

# 堆栈队列:后进先出
q = queue.LifoQueue(4)

q.put(11)
q.put(22)
q.put(33)
q.put(44)
print(q.get())
print(q.get())
print(q.get())
print(q.get())

# 优先级队列
q = queue.PriorityQueue(4)

q.put((22,'wwww'))
q.put((-2,'eeee'))
q.put((222,'qqqq'))
print(q.get())
print(q.get())
print(q.get())
''' 优先级队列put方法要传入一个元组,该元组第一个值表示数据提取的优先级,可以为负数,且数字越小,优先级越高'''

 

5、线程池与进程池

   -线程池和进程池:开设固定数量的线程或进程来执行任务的方式就称之为池。

    池的提出是为基于计算机硬件实际出发的。

    因为无论是开设进程或是线程,都会损耗系统资源(线程开销小,但是仍会有消耗),如果提交一个任务就开设一个进程或者进程,那么当提交的任务的数量很多的时候,所要开设的进程

    和线程数量会非常多,这就会导致系统资源被占用过多。

    于是提出了池,用固定数量的线程或是进程来执行那些任务。

    显然,池的使用会拖慢程序执行的效率,但是保证了计算机硬件的安全

  

  线程池,进程池的开启需要导入concurrent.futures模块

  开启进程池

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

def task(i):
    print('{}当前进程号: {}'.format(i,os.getpid()))
    print('{}当前线程号: {}'.format(i,current_thread()))
    print('==========')
    time.sleep(2)

if __name__ == '__main__':
    pool = ProcessPoolExecutor(5)  # 开启有五个子进程的进程池
    for i in range(20):  # 循环提交任务
        pool.submit(task,i)  # (要提交的任务,任务需要的参数)

  开启进程池

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

def task(i):
    print('{}当前进程号: {}'.format(i,os.getpid()))
    print('{}当前线程号: {}'.format(i,current_thread()))
    print('==========')
    time.sleep(2)

if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)  # 开启有五个子进程的进程池
    for i in range(20):  # 循环提交任务
        pool.submit(task,i)  # (要提交的任务,任务需要的参数)

 

  -线程池与线程池拿到任务执行的结果(这里可以理解为函数的返回值)

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

def task(i):
    print('{}当前进程号: {}'.format(i,os.getpid()))
    print('{}当前线程号: {}'.format(i,current_thread()))
    print('==========')
    time.sleep(2)

if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)  # 开启有五个子进程的进程池
    for i in range(20):  # 循环提交任务
        res = pool.submit(task,i)  # res拿到的不是task函数的返回值,而是一个执行结果对象<Future at 0x3c1d688 state=pending>
        print(res)

      -要想得到执行结果,在对象下使用成员方法result获取结果

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

def task(i):
    print('{}当前进程号: {}'.format(i,os.getpid()))
    print('{}当前线程号: {}'.format(i,current_thread()))
    print('==========')
    time.sleep(2)

if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)  # 开启有五个子进程的进程池
    for i in range(20):  # 循环提交任务
        res = pool.submit(task,i)  # res拿到的不是task函数的返回值,而是一个执行结果对象
        print(res.result())

      -如果使用上述代码,会发现程序的执行变成了串行。这是因为当执行到res = pool.submit(task,i)时,相当于一个线程开始执行task任务,执行下一行代码print(res.result())时,要打印task任务的

      执行结果,那么必须保证task任务执行完毕才行,此时程序会在res.result处有阻塞,直到线程完成task任务,才进入下一次for循环,开启新的任务。因此程序变成了串行。

 

  -于是我们要保证先使所有任务并发之后,再获取执行结果,可以利用之前的列表缓存方法

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

def task(i):
    print('{}当前进程号: {}'.format(i,os.getpid()))
    print('{}当前线程号: {}'.format(i,current_thread()))
    print('==========')
    time.sleep(2)

if __name__ == '__main__':
    pool = ThreadPoolExecutor(5)  # 开启有五个子进程的进程池
    l = []
    for i in range(20):  # 循环提交任务
        res = pool.submit(task,i)  # res拿到的不是task函数的返回值,而是一个执行结果对象
        l.append(res)

    for obj in l:
        print(obj.result())

    -最好的方式是使用回调机制,保证异步的任务一旦完成,就将结果对象(不是函数的返回值,要想使用返回值,调用result)自动传入到回调函数中去,并且执行回调函数

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
from threading import current_thread
import time

# 我是回调函数
def call_back(obj): print(obj.result() * 2) def task(i): # print('{}当前进程号: {}'.format(i,os.getpid())) # print('{}当前线程号: {}'.format(i,current_thread())) return i if __name__ == '__main__': pool = ThreadPoolExecutor(5) for i in range(20): res = pool.submit(task,i).add_done_callback(call_back) # 为任务绑定回调函数

 

  6、协程

    -协程并不真正存在,协程的本质是一种特殊的线程,是程序员们提出的;

    -协程可以从代码级别实现单线程遇到阻塞时,进程执行代码的切换+保存状态;

    -协程可以在单线程下实现并发效果

 

    ps:多线程下,如果一个线程遇到io阻塞,操作系统虽然会自动切换cpu使用权限到其它线程,但是这种切换比起代码间切换有两种问题

      -cpu切换是由操作系统完成的,切换花费的时间要比单线程下不同位置的代码切换要慢的多;

      -操作系统要统筹全局,可能这次cpu切换不是切换到当前进程下的其它线程,而是切换到了其它进程下的线程。

 

    要使用协程,需要导入gevent模块;在此之前,先导入monkey模块,使用monkey(猴子补丁)下的patch_all方法,使gevent模块能够在代码层面识别io阻塞

 

    开启协程

from gevent import monkey
monkey.patch_all()
import gevent
import time

def func1():
    while 1:
        print('函数1开始执行')
        time.sleep(2)
        print('函数1执行结束')

def func2():
    while 1:
        print('函数2开始执行')
        time.sleep(2)
        print('函数2执行结束')

g1 = gevent.spawn(func1)
g2 = gevent.spawn(func2)
g1.join()
g2.join()

      注意的是协程默认设置为 守护线程

 

7、利用协程实现TCP服务端并发

from gevent import monkey
monkey.patch_all()
import socket
import gevent

def talk(conn):
    while 1:
        try:
            data = conn.recv(1024)
            if not len(data):
                conn.close()
                break
            print(data.decode('utf-8'))
            conn.send(b'hello world')
        except ConnectionResetError as e:
            print(e)
            conn.close()
            break

def service():
    while 1:
        conn, addr = server.accept()
        gevent.spawn(talk,conn)
        # return 1 当函数有返回值时,会导致函数执行完毕

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen()

g1 = gevent.spawn(service)
g1.join()

 

  -执行的函数如果带有返回值,func2"起死回生"

from gevent import monkey
monkey.patch_all()
import gevent
import time

def func1():
    while 1:
        print('函数1开始执行')
        time.sleep(2)
        print('函数1执行结束')

def func2():
    while 1:
        print('函数2开始执行')
        time.sleep(2)
        print('函数2执行结束')
        return

g1 = gevent.spawn(func1)
g2 = gevent.spawn(func2)
g1.join()
g2.join()

 

posted @ 2021-02-25 12:39  口乞厂几  阅读(67)  评论(0)    收藏  举报