Python-并发编程

多线程

开启进程的两种方式
方式一
from multiprocessing import Process
import time
def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is done')
if __name__ == '__main__':
    p = Process(target=task,args=('alex',))
    p.start()
    print('我是主进程')
方式二
from multiprocessing import Process
import time
class MyProcess(Process):
    def __init__(self,name):
        self.name = name
        super(MyProcess, self).__init__()
    def run(self) -> None:
        print(f'{self.name} is running')
        time.sleep(3)
        print(f'{self.name} is done')
if __name__ == '__main__':
    p = MyProcess('alex')
    p.start()
    print('woshizhujincheng')

.join()方法
from multiprocessing import Process
import time
def task(arg):
    print('子进程开始')
    time.sleep(3)
    print('子进程结束')
if __name__ == '__main__':
    p = Process(target=task,args=('alex',))
    p.start()
    p.join() # 等待p子进程运行结束之后 再执行主进程代码
    print('主进程开始\n主进程结束')

守护进程 主进程执行结束 守护进程就结束
    1 守护进程会在主进程执行代码完毕后终止
    2 守护进程内无法再开启子进程 否则抛出异常 AssertionError: daemonic processes are not allowed to have children
    3 进程之间是相互独立的 主进程代码运行结束 守护进程随即终止
from multiprocessing import Process
import time
import random
class piao(Process):
    def __init__(self,name):
        self.name = name
        super().__init__()
    def run(self) -> None:
        print(f'{self.name} is piao')
        time.sleep(random.randrange(1,3))
        print(f'{self.name} is done')
p = piao('alex')
p.daemon = True # 要在start前设置
p.start()
print('主进程')

进程同步(加锁)
from multiprocessing import Process
from multiprocessing import Lock
import os
import time
def work(lock):
    lock.acquire()
    print(f'{os.getpid()} is running')
    time.sleep(2)
    print(f'{os.getpid()} is done')
    lock.release()
if __name__ == '__main__':
    lock = Lock()
    for i in range(3):
        p = Process(target=work,args=(lock,))
        p.start()

队列(生产者消费者模型)
from multiprocessing import Queue
import time
import random
import os
def consumer(q):
    while True:
        res = q.get()
        if res is None : break # #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res = f'{i} 包子'
        q.put(res)
        print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))
    q.put(None) #发送结束信号
if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer,args=(q,))
    c1 = Process(target=consumer,args=(q,))
    p1.start()
    c1.start()
    # p1.join()
    # q.put(None)
    print(
        'main'
    )

进程池(apply-同步调用 apply_async-异步调用)
异步调用
from multiprocessing import Process,Pool
import time
def func(msg):
    print( "msg:", msg)
    time.sleep(1)
    return msg
if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res=pool.apply_async(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res)
        # s = res.get() #如果直接用res这个结果对象调用get方法获取结果的话,这个程序就变成了同步,因为get方法直接就在这里等着你创建的进程的结果,第一个进程创建了,并且去执行了,那么get就会等着第一个进程的结果,没有结果就一直等着,那么主进程的for循环是无法继续的,所以你会发现变成了同步的效果
    print("==============================>") #没有后面的join,或get,则程序整体结束,进程池中的任务还没来得及全部执行完也都跟着主进程一起结束了
    pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果
    for i in res_l:
        print(i.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
同步调用
from multiprocessing import Process,Pool
import time
def func(msg):
    print( "msg:", msg)
    time.sleep(0.1)
    return msg
if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res=pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个
    print("==============================>")
    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print(res_l) #看到的就是最终的结果组成的列表
    for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法
        print(i)

线程

1 开启线程的两种方式
方式一
from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print(f'{name} say hi ')
if __name__ == '__main__':
    t = Thread(target=sayhi,args=('alex',))
    t.start()
    print('主线程')
方式二
from threading import Thread
import time
class SayHi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self) -> None:
        time.sleep(2)
        print(f'{self.name} say Hi')
if __name__ == '__main__':
    t = SayHi('alex')
    t.start()
    print('主线程')
2 .join()方法
from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print(f'{name} say hello')
if __name__ == '__main__':
    t = Thread(target=sayhi,args=('alex',))
    t.start()
    t.join()
    print('主线程')
    print(t.is_alive())
3 守护线程
'''
无论是进程还是线程 都遵循 守护xx会等待主xx运行完毕后销毁
***运行完毕并非终止运行
1 对于主进程来说 运行完毕是指主进程代码执行完毕
    主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收) 然后主进程会一直等待非守护的
    子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程)才会结束
2 对主线程来说 运行完毕是指主线程所在的进程内所有的非守护线程运行完毕 主线程才算运行完毕
    主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收) 因为主线程的结束意味着
    进程的结束 进程的整体资源都将被回收 而进程必须保证非守护线程都运行完毕后才能结束
'''
from threading import Thread
import time
def sayhi(name):
    time.sleep(3)
    print(f'{name} say hi ')
if __name__ == '__main__':
    t = Thread(target=sayhi,args=('alex',))
    t.setDaemon(True) #必须在t.start()之前设置
    t.start()
    print('主线程')
    print(t.is_alive())
4 互斥锁
from threading import Lock
from threading import Thread
import time
x = 100
lock = Lock()
def task():
    global x
    lock.acquire()
    temp = x
    time.sleep(0.1)
    temp = -1
    x = temp
    lock.release()
if __name__ == '__main__':
    t_l1 = []
    for i in range(100):
        t = Thread(target=task)
        t_l1.append(t)
        t.start()
    for i in t_l1:
        i.join()
    print(f'主{x}')
5 死锁与递归锁
from threading import Thread
from threading import RLock
import time
lock_A = lock_B = RLock()
class MyThread(Thread):
    def run(self) -> None:
        self.f1()
        self.f2()
    def f1(self):
        lock_A.acquire()
        print(f'{self.name} 拿到A锁')
        lock_B.acquire()
        print(f'{self.name} 拿到B锁')
        lock_B.release()
        print(f'{self.name} 释放B锁')
        lock_A.release()
        print(f'{self.name} 释放A锁')
    def f2(self):
        lock_B.acquire()
        print(f'{self.name} 拿到B锁')
        time.sleep(0.1)
        lock_A.acquire()
        print(f'{self.name} 拿到A锁')
        lock_A.release()
        print(f'{self.name} 释放A锁')
        lock_B.release()
        print(f'{self.name} 释放B锁')
if __name__ == '__main__':
    for i in range(3):
        t = MyThread()
        t.start()
    print('zhu')
6 信号量
'''
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
'''

from threading import Thread
from threading import Semaphore
from threading import current_thread
import time
import random
sem = Semaphore(5)
def go_public_wc():
    sem.acquire()
    print(f'{current_thread().getName()} wcing')
    time.sleep(random.randint(1,3))
    sem.release()
if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=go_public_wc)
        t.start()
7 GIL VS Lock
'''
    机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?

 首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

    然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

 最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

  线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果

  既然是串行,那我们执行

  t1.start()

  t1.join

  t2.start()

  t2.join()

  这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。

因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以
被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing
时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,
即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,  这可以说是Python早期版本的遗留问题。

'''
8 事件
'''
同进程的一样

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变
得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,
Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将
一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行


event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。
'''
9 条件condition
import time
from threading import Thread,RLock,Condition,current_thread

def func1(c):
    c.acquire(False) #固定格式
    # print(1111)

    c.wait()  #等待通知,
    time.sleep(3)  #通知完成后大家是串行执行的,这也看出了锁的机制了
    print('%s执行了'%(current_thread().getName()))

    c.release()

if __name__ == '__main__':
    c = Condition()
    for i in range(5):
        t = Thread(target=func1,args=(c,))
        t.start()

    while True:
        num = int(input('请输入你要通知的线程个数:'))
        c.acquire() #固定格式
        c.notify(num)  #通知num个线程别等待了,去执行吧
        c.release()
10 定时器
from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    count=1
    while not event.is_set():
        if count > 3:
            raise TimeoutError('链接超时')
        print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count))
        event.wait(0.5)
        count+=1
    print('<%s>链接成功' %threading.current_thread().getName())


def check_mysql():
    print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName())
    time.sleep(random.randint(2,4))
    event.set()
if __name__ == '__main__':
    event=Event()
    conn1=Thread(target=conn_mysql)
    conn2=Thread(target=conn_mysql)
    check=Thread(target=check_mysql)

    conn1.start()
    conn2.start()
    check.start()
11 线程队列
	1 先进先出
	2 先进后出
	3 优先级队列
12 Python标准模块

协程

1 介绍
'''
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
#1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
#2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的
    #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
    #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换
  优点如下:
        #1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
        #2. 单线程内就可以实现并发的效果,最大限度地利用cpu
   缺点如下:
        #1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
协程特点
    1 必须在只有一个单线程里实现并发
    2 修改共享数据不需加锁
    3 用户程序里自己保存多个控制流的上下文栈
'''
2 greenlet
#真正的协程模块就是使用greenlet完成的切换from greenlet import greenlet
from greenlet import greenlet
def eat(name):
    print('%s eat 1' %name)  #2
    g2.switch('taibai')   #3
    print('%s eat 2' %name) #6
    g2.switch() #7
def play(name):
    print('%s play 1' %name) #4
    g1.switch()      #5
    print('%s play 2' %name) #8

g1=greenlet(eat)
g2=greenlet(play)
g1.switch('taibai')#可以在第一次switch时传入参数,以后都不需要  
3 gevent
# 应用一 爬虫
from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time
def get_page(url):
    print('GET: %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' %(len(response.text),url))
start_time=time.time()
gevent.joinall([
    gevent.spawn(get_page,'https://www.python.org/'),
    gevent.spawn(get_page,'https://www.yahoo.com/'),
    gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
# 应用二 并发开启网络通信的客户端
client
from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
    c.connect((server_ip,port))

    count=0
    while True:
        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
        msg=c.recv(1024)
        print(msg.decode('utf-8'))
        count+=1
if __name__ == '__main__':
    for i in range(500):
        t=Thread(target=client,args=('127.0.0.1',8080))
        t.start()
server
from gevent import monkey;monkey.patch_all()
from socket import *
import gevent

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)
def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()
if __name__ == '__main__':
    server('127.0.0.1',8080)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值