meylovezn 2020-02-10
############### 线程和GIL,全局解释器锁 ##############
""" 线程 为什么会有进程? 主要是能够同时处理多个任务,多个任务还要进行切换,时间片轮转 为什么会有线程? 进程并不是执行任务的最小单元,每一个进程里面有都一个线程,我们叫做主线程, 早期没有线程,一个进程只能干一个任务,如果有多个任务,只能多起进程,进程太多不行的, 进程内部的内存是共享的,所以需要线程,否则还要涉及到进程间的通信,这比较浪费资源 所以线程的出现解决了两个问题: 1,进程内部的通信 2,一个进程可以处理多个任务, 线程的开销比进程少,可以认为是一个轻型的进程, 进程可以任务是车间,线程可以认为是每一个工人, 进程是资源分配的最小单位,线程是cpu调度的最小单位, ########################### 进程和线程的区别: 1,进程之间的内存是独立的,但是一个进程之内的线程是可以共享的, 2,进程之间切换是慢于线程之间的切换的, """ # 第一个例子 from threading import Thread import time,os # 多线程并发 # def func(n): # 这是子线程完成的 # time.sleep(1) # 虽然是打印了10次,但是只等待了1秒,所以10线程之间是并发的, # print(n) # # for i in range(10): # 启动10个线程, # t = Thread(target=func,args=(1,)) # 注册 # t.start() # 这是启动了一个线程 # 第二种启动线程的方法: # class MyTread(Thread): # # def __init__(self,arg): # super().__init__() # self.arg=arg # def run(self): # time.sleep(1) # print(1,os.getpid()) # # # for i in range(10): # t = MyTread(10) # 传递参数 # t.start() # # print("主线程",os.getpid()) # 打印子进程和主进程的进程号,都是一样的 # 进程里面存了导入的模块,文件所在的位置,内置的函数代码 # 线程里面存了少量的必不可少代码,比如做加法运算,需要的参数, # 所以目前看起来进程和线程的启动是非常的类似的, ######################################### # 同一个数据,多个线程去操作,也会出现问题,所以也有线程锁的概念, # 这种机制叫做全局解释器锁,英文GIL, # 这种机制的结果就是:同一时间只有一个线程去访问cpu, # 你想要访问数据,就必须拿到这个钥匙, # 这个锁,所的是线程,不是锁的某一个数据, # 这样不好,因为同一时间cpu上面只有一个线程,这样不能充分的利用cpu # 这不是Python语言的问题,这是cPython解释器的问题,如果你有一个jPython解释器就行 # 这的确是一个弊病,导致Python的多线程形同虚设, # 那么为什么不解决呢? # java和c++都是编译性语言,Python是一个解释性语言,php也是, # 目前解释性语言就是存在这个问题,这个矛盾还不可调和, # 是否把数据加锁就可以了,也是不行的,数据太多了,这么大范围的加锁,最终导致的效率,还不如全局解释器锁的性能好, # 在cPython解释器下的Python程序,在同一时间,多个线程只能有一个线程在cpu执行, # 这不意味着多线程就没有意义了, # 因为只有涉及到计算的地方才会使用到CPU, # 高CPU:所以在计算类的高cpu利用率的,Python不占优势, # 高IO:我们写的代码很多都涉及到这种, # 比如qq聊天,处理日志文件,爬取网页,处理web请求,读写数据库,都是高io的,都是有Python用武之地的, # 所以Python不能处理计算类高的问题,这不影响他在web编程的作用, # 如果你真的需要高并发呢,你可以使用多进程,就不会有GIL锁了, ####################################### # Threading中的其他方法 import threading def func(n): print(n,threading.current_thread()) # <Thread(Thread-1, started 5428)> for i in range(10): threading.Thread(target=func,args=(1,)).start() print(threading.active_count()) # 查看活跃的线程数, print(threading.current_thread())
############### 守护线程 ##############
# 守护线程: from threading import Thread import time def func1(name): while True: print(11111111) time.sleep(1) def func2(name): print(2222222) time.sleep(5) if __name__ == ‘__main__‘: t=Thread(target=func1,args=(‘andy‘,)) t.daemon = True # 主线程代码结束,子线程随之结束, # 不加守护线程,主线程就会等待子线程的结束,然后主线程才会结束, t.start() t2=Thread(target=func2,args=(‘lucy‘,)) t2.start() # 主线程会等待子线程结束 print(‘主线程‘) print(t.is_alive()) ‘‘‘ 主线程 True ‘‘‘ # 所以进程的守护进程,和线程的守护进程是有不一样的点的 # 1.对主进程来说,运行完毕指的是主进程代码运行完毕 # 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕 # 这是一个盲点,后续需要再看看,
############### 线程锁---互斥锁,死锁,递归锁, ##############
# 线程锁 # 你用进程锁的时候不多,线程锁用的多 # 为什么会用到线程锁? # 就是因为多个线程同时操作一个资源会出问题, # 一般的锁,lock,叫做互斥锁,就是说你只能拿到你把钥匙, # 死锁 # 递归锁,你拿到一把钥匙,就拿到了这个钥匙串,别的线程就拿到不到这个钥匙串了, from threading import Lock,Thread import time # 模拟多线程出错的场景, # 全局的n是10,然后创建10个线程,去执行n-1的操作, # 结果按理说应该是0,但是结果是9, # 为什么? # 10个线程去拿n=10,所有人拿的都是10,然后都同步减去1,然后返回都是9,所以是9, # 但是有GIL锁啊,为什么还会产生这种情况? # 还是因为时间片轮转,你取到了数据,但是还没有来得及减1,这个值就被其他的线程拿走了,还是10, # # def func(): # global n # temp = n # time.sleep(0.2) # n= temp-1 # # n = 10 # t_list=[] # for i in range(10): # t= Thread(target=func) # t.start() # t_list.append(t) # # # for i in t_list:t.join() # print(n) # 模拟线程加锁的情况,然后看看结果 # 这样加了锁之后,结果应该就是0了,是的,结果是0了 # 加锁降低了性能,但是保证了数据的安全性, # def func(lock): # global n # lock.acquire() # temp = n # time.sleep(0.2) # n= temp-1 # lock.release() # # n = 10 # t_list=[] # lock = Lock() # # for i in range(10): # t= Thread(target=func,args=(lock,)) # t.start() # t_list.append(t) # # # for i in t_list:t.join() # print(n) # 现在看看科学家吃面的问题,看看死锁的场景 # 这个例子是只有拿到面和叉子,才可以吃面, # # # noodle_lock = Lock() # fork_lock = Lock() # def eat1(name): # noodle_lock.acquire() # print("%s拿到面条"%name) # fork_lock.acquire() # print("%s拿到叉子"%name) # print("吃面") # fork_lock.release() # noodle_lock.release() # # # # def eat2(name): # fork_lock.acquire() # print("%s拿到叉子"%name) # time.sleep(1) # noodle_lock.acquire() # print("%s拿到面条"%name) # print("吃面") # noodle_lock.release() # fork_lock.release() # # Thread(target=eat1,args=("name1",)).start() # Thread(target=eat2,args=("name2",)).start() # Thread(target=eat1,args=("name3",)).start() # Thread(target=eat1,args=("name4",)).start() # # """ # name1拿到面条 # name1拿到叉子 # 吃面 # name2拿到叉子 # name3拿到面条 # 这种情况不同的人拿到了叉子和面条,就无法进行了,就阻塞了,就死锁了, # 怎么解决?有一个递归锁 # """ # 递归锁,来解决死锁的问题 # # 你可以看做是钥匙串上面的两把钥匙,一旦你拿到了一把钥匙,就证明你拿到了整个钥匙串了, from threading import RLock noodle_lock = fork_lock = RLock() # 你可以看做是钥匙串上面的两把钥匙 def eat1(name): noodle_lock.acquire() print("%s拿到面条"%name) fork_lock.acquire() print("%s拿到叉子"%name) print("吃面") fork_lock.release() noodle_lock.release() def eat2(name): fork_lock.acquire() print("%s拿到叉子"%name) time.sleep(1) noodle_lock.acquire() print("%s拿到面条"%name) print("吃面") noodle_lock.release() fork_lock.release() Thread(target=eat1,args=("name1",)).start() Thread(target=eat2,args=("name2",)).start() Thread(target=eat1,args=("name3",)).start() Thread(target=eat2,args=("name4",)).start()
############### 信号量和事件 ##############
# 信号量 # 信号量就是控制只能有n个线程能访问这段代码 # from threading import Semaphore,Thread # import time # def func(sem,a,b): # sem.acquire() # time.sleep(1) # print(a+b) # sem.release() # # sem = Semaphore(4) # for i in range(10): # t = Thread(target=func,args=(sem,i,i+5)) # t.start() # 事件: # 事件被创建的时候是false状态,这个false状态会导致wait被阻塞, # true状态的时候,wait就不阻塞了 # clear,设置为false # set,设置状态未false, # 上次举例的是红绿灯的例子, # 现在我们举一个例子,检测数据库的可连接情况 # 启动两个线程, # 第一个线程连接数据库, # 等待一个信号,告诉我们之间的网络是通的 # 第二个线程,检测和数据自己之间的网络是否是通的 # 通了之后把事件的状态改为true, import time,random from threading import Thread,Event def connect_db(e): count = 0 while count < 3: # e.wait() # 这个是一直等待,这种比较浪费资源, e.wait(0.1) # 这是我只等待1秒,否则我就不等你了,# 但是我应该有一个重连的过程,比如三次,三次都连接不上,就断掉了 if e.is_set() == True: print("连接数据库") break else: print("连接失败") count += 1 else: raise TimeoutError("数据库连接超时") # 主动报异常 def check_web(e): time.sleep(random.randint(0,2)) e.set() e = Event() t1 = Thread(target=connect_db,args=(e,)) t2 = Thread(target=check_web,args=(e,)) t1.start() t2.start()
############### 线程的条件和定时器 ##############
# 条件 from threading import Condition,Thread,Timer # 可以把条件视为一个更加复杂的锁 # 同时也有两个方法,acquire,release # 一个条件被创建,默认也是一个false状态,会影响wait处于等待状态 # notify(int),这是制造钥匙,参数是制造多少把钥匙 # 这个几乎就是只会出现在面试里面,平时基本不会用到, # # def func(con,i): # con.acquire() # con.wait() # 在等待钥匙, # print("在第%s个循环里"%i) # con.release() # # # con = Condition() # for i in range(10): # Thread(target=func,args=(con,i)).start() # # # while True: # num = int(input(">>>")) # con.acquire() # con.notify(num) # 制造钥匙, # con.release() # 定时器: def func(): print("时间同步") Timer(2,func).start() # 等待两秒钟开启一个线程,
############### 线程,队列 ##############
# 线程,队列 # 加锁会自己写代码,不方便,我们可以使用队列 # 队列内置了很多的锁,可以保证数据安全, import queue q = queue.Queue() q.put() q.put_nowait() # 这个会报错 q.get() q.get_nowait() # 这个会报错, # 队列的特点:先进先出, queue.LifoQueue() # 栈,先进后出, q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) queue.PriorityQueue() # 优先级队列, # 放数据的视乎,除了数据,还需要一个参数:优先级 q.put(20,1) q.put(10,2) # 数字越小,优先级越高,优先级一样,就是按照和ASk码排, q.put(30,3) print(q.get()) print(q.get()) print(q.get()) # 总结: # 1,普通队列 # 2,栈 # 3,优先级队列, # 这三种都不会出现多线程抢占资源,
############### 线程池 ##############
# 线程池 # 新的一个模块, # 1 介绍 # concurrent.futures模块提供了高度封装的异步调用接口 # ThreadPoolExecutor:线程池,提供异步调用 # ProcessPoolExecutor: 进程池,提供异步调用 from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(2) print(n) return n*n tpool = ThreadPoolExecutor(max_workers=5) # 进程池,启动cpu核数+1. # 而线程池的启动是cpu核数 * 5 不要超过这个, t_list = [] for i in range(20): t = tpool.submit(func,i) # 提交一个任务,传递一个参数, t_list.append(t) tpool.shutdown() # shutdown做了两个事情: # 1,colse 关闭这个池子,不让有任务进来, # 2,join是阻塞,直到这个池子的任务执行完, # 所以是一个shutdown做了两个事情, print("主进程") for t in t_list:print(t.result())
############### 进程和线程的对比 ##############
进程和线程的对比: 面试的时候会问到, 进程是很多资源的总称,包括代码,包括显示器,包括鼠标,键盘等 线程是轻型的实现多任务的方式, qq程序的多开,就是多个进程, 一个进程里面可以多个任务,这就是线程, ---------------------------- 进程先有的,然后才有线程, 进程是一个资源分配的单位,具体谁拿着资源去做的呢,是线程, 一个进程里面一定有一个主线程, -------------- 所以多线程能实现多任务,是指的在一个进程里面有多个线程,实现多任务, 多进程的多任务,是有开辟了一个资源分配的单位,实现多任务,这个时候一个进程里面可能只有一个主线程, 真正执行代码的时候还是线程, ------------- 所以进程需要很大的资源才可以实现多任务,但是线程只需要很少的资源就可以实现多任务, 就像是同一个流水线,然后有很多的工人, 流水线就是进程,流水线上的工人就是一个线程, 所以开发的时候最长使用的是线程,这个比较轻, ------------ 比如一个网易云音乐,既要下载音乐,又要播放音乐, 这就是一个进程,两个线程, 进程之间是独立的, 线程是依赖于进程的,没有进程就没有线程, ---------------------- 所以这是非常的重要的,
############### 网络编程 ##############