复习打卡--0821多线程并发

 一、并发和并行

并发:任务数>CPU核数,通过系统的各任务调度算法,来回切换,实现多个任务“一起”运行,实际上不是真正同时一起运行,只是切换运行的速度相当快,看上去是一起执行的而已;

并行:任务数<=CPU核数,是真正的一起同时运行。

同步:同步是指代码调用IO操作时,必须等待IO操作完成返回才调用的方式,只有一个主线;

异步:异步是指代码调用IO操作时,不必等待IO操作完成返回才调用的方式,存在多条主线;

from threading import Thread
import time

def timer(fun):
    def wrapper(*args,**kwargs):
        time1 = time.time()
        fun(*args,**kwargs)
        time2=time.time()
        print("当前函数运行时间为:{}".format(time2-time1))
        return time2-time1
    return wrapper

def work1():
   for i in range(6):
        time.sleep(1)
        print(f'第{i}次浇花中')

def work2(name):
   for i in range(5):
        time.sleep(1)
        print(f'第{i}次{name}打墙中')

# 同步运行如下,需用11s多
@timer
def main():
    work1()
    work2() 

#异步运行,只用6s多
@timer
def main2():
    t1=Thread(target=work2,args=('musen',))  # 线程执行函数的传参或者 Thread(target=work2,kwargs={'name':'musen'}) 这种方式传参
 t1.start() # 初始新线程的准备工作并执行,不一定在主线程后执行,也有可能先于主线程 print("打墙任务异步执行中") work1() main() main2()

threading模块讲解:

创建线程对象: t1=threading.Thread(target=func)    func为指定线程执行的任务函数

Thread类常用方法、属性

1.start() :启动线程活动。
2.run():这个方法描述线程的活动,可以在子类中覆盖这个方法。
3.join(timeout=None):设置主线程等待子线程执行结束后再继续运行。可以设定一个timeout参数,避免无休止的等待。因为两个线程顺序完成,看起来象一个线程,所以称为线程的合并.
通过传给join一个参数来设置超时,也就是超过指定时间join就不在阻塞进程。而在实际应用测试的时候发现并不是所有的线程在超时时间内都结束的,而是顺序执行检验是否在time_out时间内超时,例如,超时时间设置成2s,前面一个线程在没有完成的情况下,后面线程执行join会从上一个线程结束时间起再设置2s的超时。
4.name:线程名
5.getname(): 返回线程名
6.setname(): 设置线程名
7.ident:线程的标识符
8.is_alive(): 返回线程是否活动
9.daemon:布尔值,表示这个线程是否是守护线程

threading.active_count():这个函数返回当前线程的个数,这个程序的值也等于列表 

threading.current_thread():返回当前的线程对象

threading.enumereate():返回当前运行线程的列表

threading.main_thread():返回主线程,正常情况下,主线程就是python 解释器运行时开始的那个线程。

# 定义线程类
import
time from threading import Thread def timer(fun): def wrapper(*args,**kwargs): time1 = time.time() fun(*args,**kwargs) time2=time.time() print("当前函数运行时间为:{}".format(time2-time1)) return time2-time1 return wrapper class MyTread(Thread):
   def __init__(self):
    super().__init__(*args,**keargs)
    self.url=url
def run(self): headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 afari/537.36"} for i in range(100): #res=requests.get('https://www.baidu.com',headers=headers)
     res=requests.get(url=self.url,headers=headers) 
print(res) @timer def main(): t_list=[] for i in range(10): #t=MyTread()
     t=MyTread('https://www.baidu.com') t.start()
#t.join() 如果放在这里,就会变成单线程运行,一个执行完再建一个子线程 # 遍历所有子线程,设置子线程全部执行完后再执行主线程 for j in t_list: j.join() if __name__=="__main__": main()

多线程可以共享全局变量(使用同一块内存),修改会造出资源竞争,因此python同一进程里的多线程不可能实现并行,只能是并发,在线程中来回切换。

线程之间哪些操作会引起切换?

1. IO耗时操作:网络、文件、输入等耗时的IO操作,会自动进行线程切换;

2. 线程执行时间达到一定的阀值时会执行切换;

100万bug的解决方法:

1. 通过加锁来处理

2. 通过存储队列来做

from threading import Thread,Lock

num=0

def work1():
global num
for i in range(1000000): meta.acquire() # 加锁 num+=1 meta .release() # 解锁 def work2():
   global num
for i in range(1000000): meta.acquire() # 加锁 num+=1 meta .release() # 解锁 meta=Lock() # 创建一把锁 def main(): t1=Thread(target=work1) t2=Thread(target=work2) t1.start() t2.start() t1.join() t2.join() print(num) if __name__=='__main__': main()

GIL全局解释器锁:

IO密集型任务: CPU占用少,大部分时间在IO操作等待上。这种适合多线程来完成

CPU密集型任务:CPU占用多,需要进行大量的计算。这种适合单线程来完成。

关于GIL的几点说明:

1.python语言和GIL没有关系,仅仅是由于历史原因在Cpython虚拟机(解释器)上,难以移除GIL;

2.GIL:全局解释器锁,每个线程在执行中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码;

3. 线程释放GIL锁的情况,在IO等操作可能引起阻塞的system call之前,可以暂时释放GIL,执行完毕后必须获取GIL,python3使用计时器执行时间达到阀值时(python2使用tickets记数达到100)当前线程释放GIL

4. python多进程可以使用多核的CPU资源

死锁:

在多线程中,线程可以通过互斥锁来保证对同一资源的唯一占有,但当程序变得复杂后,可能会出现线程 A 对资源 A 上了锁,而线程 A 后边需要用到资源 B,使用完毕后才会对资源 A解锁,而线程 B 对资源 B 上了锁,它后边选要用到资源 A,用过后才会给 B 解锁,如果线程 A 和线程 B 同时运行,就可能会造成一种情况:线程 A 在等待线程 B 解锁,线程 B 也在等待线程 A 解锁,这就是死锁问题。

解决方法:​ 死锁问题应该尽量在设计程序时避免,或添加等待超时时间,从而检测程序是否产生了死锁,另一种就是通过银行家算法也可以避免死锁问题

银行家算法:​ 银行家算法的思想就是,假设银行有 10 元,这个时候有三个人提出贷款,A 要贷款 9 元,B 要贷款 3 元,C 要贷款 8 元,这时,银行肯定不够将所有人都满足,银行家算法就诞生了

​ 这时银行为了留住所有客户并且保证自己的钱不会不足,便分批贷款给客户,先借给 A 2 元、B 2 元、C 4 元,银行还剩 2 元,此时 B 直需要再借 1 元就满足了他自己的需求,银行便借给他 1 元,自己剩 1 元,当 B 用完,将 3 元还给银行后,银行再将这 4 元借给 C,C 也就满足了,等 C 还款后,再将 8 元中的 7 元借给 A,这样便动态的满足了三个客户的需求

​ 银行家算法在程序中实际上也是模拟了银行贷款的过程,操作系统会动态的向各个线程分配资源,在分配前,系统会判断分配后会不会导致系统进入不安全状态,不会就分配资源给线程,会则令线程等待

猜你喜欢

转载自www.cnblogs.com/qingyuu/p/12255578.html