python并发编程(1)多线程

一、线程的创建

原理:线程抢夺cpu时间片,谁抢到了谁就执行,在python中,当一个线程运行100个字节后,会自动释放时间片。重新抢夺。

  1. 启动多个线程(函数方式)
  • 在Python3中,Python提供了一个内置模块 threading.Thread,可以很方便地让我们创建多线程。 一般接收两个参数
  1. 线程函数名:要放置线程让其后台执行的函数,由我们自已定义,注意不要加();
  2. 线程函数的参数:线程函数名所需的参数,以元组的形式传入。若不需要参数,可以不指定。
import threading
import time

def run(n):
    print('task{}'.format(n))
    time.sleep(2)

t1 = threading.Thread(target= run,args = (1,))
t2 = threading.Thread(target = run,args = (2,))

t1.start()
t2.start()
  • 这样我们会发现,两个线程一起执行,同时得出结果。
  1. 启动多个线程(类方式)
class MyThread(threading.Thread):
    
    def __init__(self,name):
        super(MyThread,self).__init__()
        self.name = name
        
    def run(self):
        print('task',self.name)
        
t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start()

重写了init方法,所以只需要传一个参数,默认运行run方法里的内容

二、互斥锁

为什么要锁?

  • 当多个线程对同一个属性进行操作的时候,就会出现线程安全问题,这个时候就需要互斥锁。锁的是代码块,只有拿到锁的线程才能执行。
  • 被同一个锁锁住的代码块,不管有多少,在同一时刻都只能执行一个,因为同样的锁在全局中只有一个

创建锁的两种方式:

import threading
# 生成锁对象,全局唯一
lock = threading.Lock()
# 获取锁。未获取到会阻塞程序,直到获取到锁才会往下执行
lock.acquire()
# 释放锁,归回倘,其他人可以拿去用了
lock.release()
  • 需要注意的是,lock.acquire() 和 lock.release()必须成对出现。否则就有可能造成死锁。
import threading

lock = threading.Lock()
with lock:
    # 这里写自己的代码
    pass
  • with 语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。

获取一把锁

lock = threading.Lock()

lock1 = threading.Lock()
lock2 = threading.Lock()
class Test:
    
    def fun1(self):
        with lock1:
            print('fun1')
            time.sleep(1)
    
    def fun2(self):
        with lock2:
            print('fun2')
        
t = Test()

def run1():
    t.fun1()
    print(11111)
    
def run2():
    t.fun2()
    t.fun1()
    print(22222)
    
t1 = threading.Thread(target=run1)
t2 = threading.Thread(target=run2)
t1.start()
t2.start()

# 输出结果

# fun1
# 11111fun2
#
# fun1
# 22222
  1. 锁必须配套使用,两个被同一把锁锁住的方法同时只能有一个执行,两个被不同锁锁住的方法可以同时执行。当有一个线程执行fun1、时,拿走了lock1,第二个线程想要执行,就会寻找lock1锁,当线程执行完with代码块时,第一个线程会自动返回这个锁。每一个被创建的锁都只有一把,拿到的线程才能执行对应的代码块,拿不到的则不能执行。
  2. 把fun2的lock2改成lock1,那么当t1线程执行fun1中的代码块时,拿走lock1,当t2执行fun2中的代码块时,发现也需要这个锁,但是已经被t1拿走了,所以只能等待t1返还这个锁才能拿到锁执行代码。
  3. 锁一旦被创建,全局就只有一把,谁拿到谁就能执行被这个锁锁住的代码块。

三、GIL(全局锁)

多线程实际上只能由一个线程执行,就算cpu是多核的也只能运行一个核运行,这是因为有GIL(全局锁)

什么是GIL呢?
任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

四、线程通信

从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用put() 和 get() 操作来向队列中添加或者删除元素。

原理图:
在这里插入图片描述
生产者消费者模式

from queue import Queue
from time import sleep
from random import randrange
import threading

q = Queue(maxsize=10)

def producer():
    for i in range(0,10):
        product = '生产者生产产品' + str(i)
        print(product)
        q.put(product)
        sleep(randrange(3))
        
def consumer():
    for i in range(0,10):
        product = '消费者消费产品' + str(i)
        print(product)
        q.put(product)
        sleep(randrange(5))
        
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t1.start()
t2.start()
  • 也可以实例化多个消费者

四、线程池

线程池原理:

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

future对象:在未来的某一时刻完成操作的对象. submit方法可以返回一个future对象.

from concurrent.futures import ThreadPoolExecutor

def run(a,b):
    print('{}+{}={}\t{}'.format(a,b,a + b,threading.currentThread().ident))
    sleep(b)
    return a + b

ex = ThreadPoolExecutor(max_workers=3)#创建一个线程池
f1 = ex.submit(run,2,3)# 将run带参数2,3放入线程池运行
f2 = ex.submit(run,2,2)# 将run带参数2,2放入线程池运行
print(f1.done())# 检测线程f1是否结束
print(f1.result())# 获取线程f1的返回值

# 输出结果
# 2+3=5	1352
# 2+2=4	12228
# False
# 5
  1. 线程池的map函数
    语法:

线程池对象.map(函数(线程),可迭代对象)

作用:
将可迭代对象里的值每个都放入线程中执行,返回的值顺序与放入的顺序一致

list1 = ['aaa','bbb','ccc','ddd','eee','fff']
def run(a):
    print(a[0])
    return a[0]
    
ex = ThreadPoolExecutor(max_workers=3)
result = ex.map(run,list1)
sleep(1)
print('map的返回值')
for i in result:
    print(i,end=' ')
  1. as_completed()函数
  • 线程池可以同时处理多个线程,每个线程都会返回一个future对象,若我们要挨个对这些对象操作就会显得很繁琐,as_completed()就可以吧这些对象全部存在一个序列
import concurrent.futures

def run(a,b):
    return a + b

ex = concurrent.futures.ThreadPoolExecutor(max_workers=3)
f1 = ex.submit(run,1,2)
f2 = ex.submit(run,2,3)
complete = concurrent.futures.as_completed([f1,f2])
for i in complete:
    print(i.result())
    
# 输出结果
# 3
# 5
  1. 线程池的wait()
    wait 方法可以让主线程阻塞,直到满足设定的要求。wait 方法接收3个参数,等待的任务序列、超时时间以及等待条件。等待条件 reture_when 默认为 ALL_COMPLETED,表明要等待所有的任务都结束。可以看到运行结果中,确实是所有任务都完成了,主线程才打印出 main。等待条件还可以设置为 FIRST_COMPLETED,表示第一个任务完成就停止等待,默认是等待全部任务完成。
glo = 0
def run(n):
    global glo
    glo += 1
    print('第{}个完成的线程'.format(glo))
    sleep(n)

ex = concurrent.futures.ThreadPoolExecutor(max_workers=3)
task = [ex.submit(run,n) for n in range(1,3)]
concurrent.futures.wait(task)
print('所有线程执行完毕')

最后说一下回调:add_done_callback(fn) , 回调函数是在调用线程完成后再调用的,在同一个线程中.

def run():
    print('11111111')
    print('222222222')
    print('tid:',threading.currentThread().ident)
    
def call_back(obj):
    print('->>>>>>>>>call_back , tid:',threading.currentThread().ident, ',obj:',obj)

    
ex = concurrent.futures.ThreadPoolExecutor(max_workers=3)
f1 = ex.submit(run)
f1.add_done_callback(call_back)

# 输出结果

# 11111111
# 222222222
# tid: 5372
# ->>>>>>>>>call_back , tid: 10992 ,obj: <Future at 0x2897b6ec820 state=finished returned NoneType>
2020/5月/15好上传最新版 JavaGuide 目前已经 70k+ Star ,目前已经是所有 Java 类别项目 Star 数量第二的开源项目了。Star虽然很多,但是价值远远比不上 Dubbo 这些开源项目,希望以后可以多出现一些这样的国产开源项目。国产开源项目!加油!奥利给! 随着越来越多的人参与完善这个项目,这个专注 “Java知识总结+面试指南 ” 项目的知识体系和内容的不断完善。JavaGuide 目前包括下面这两部分内容: Java 核心知识总结; 面试方向:面试题、面试经验、备战面试系列文章以及面试真实体验系列文章 内容的庞大让JavaGuide 显的有一点臃肿。所以,我决定将专门为 Java 面试所写的文章以及来自读者投稿的文章整理成 《JavaGuide面试突击版》 系列,同时也为了更加方便大家阅读查阅。起这个名字也犹豫了很久,大家如果有更好的名字的话也可以向我建议。暂时的定位是将其作为 PDF 电子书,并不会像 JavaGuide 提供在线阅读版本。我之前也免费分享过PDF 版本的《Java面试突击》,期间一共更新了 3 个版本,但是由于后面难以同步和订正所以就没有再更新。《JavaGuide面试突击版》 pdf 版由于我工作流程的转变可以有效避免这个问题。 另外,这段时间,向我提这个建议的读者也不是一个两个,我自己当然也有这个感觉。只是自己一直没有抽出时间去做罢了!毕竟这算是一个比较耗费时间的工程。加油!奥利给! 这件事情具体耗费时间的地方是内容的排版优化(为了方便导出PDF生成目录),导出 PDF 我是通过 Typora 来做的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若能绽放光丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值