39 day of python
今日内容:
线程的Thread模块
同步控制:锁 事件 信号量 条件 定时器 队列
线程池/进程池
协程
网络开发中的IO模型
池的概念
回调函数:Pool这个类中 回调函数是主进程执行
如果有两个任务,我的第二个任务在第一个任务执行完毕之后能够立即被主进程执行
线程的概念
进程是操作系统中最小的资源分配单位
线程是CPU调度的最小单位
线程进程之间的对比
线程不能独立存在,必须在一个进程里
线程的开启 关闭以及切换的开销要远远小于进程
同一个进程之间的多个线程之间数据共享
全局解释器锁GIL
使得一个进程中的多个线程不能充分的利用多核
如何开启一个线程
进程好 还是 线程好?
什么时候用多进程 什么时候用多线程?
并发
CPU的使用率:计算 - 多进程
IO操作:网络\文件\数据库 - 多线程
Thread类
import time from threading import Thread from multiprocessing import Process def func(a): a = a + 1 if __name__ == '__main__': start = time.time() t_l = [] for i in range(50): t = Thread(target=func,args=(i,)) t.start() t_l.append(t) # t_l 50个线程对象 for i in t_l:t.join() print('主线程') print(time.time() - start) start = time.time() t_l = [] for i in range(50): t = Process(target=func,args=(i,)) t.start() t_l.append(t) for i in t_l:i.join() print('主进程') print(time.time() - start)
start join
terminate 在线程中没有
线程之间的数据共享
from threading import Thread n = 100 def func(): global n n -= 1 if __name__ == '__main__': t_l = [] for i in range(100): t = Thread(target=func) t.start() t_l.append(t) for t in t_l: t.join() print(n)
守护线程
import time from threading import Thread def thread1(): while True: print(True) time.sleep(0.5) def thread2(): print('in t2 start') time.sleep(3) print('in t2 end') if __name__ == '__main__': t1 = Thread(target=thread1) t1.setDaemon(True) t1.start() t2 = Thread(target=thread2) t2.start() time.sleep(1) print('主线程')
主线程如果结束了 那么整个进程就结束
守护线程 会等待主线程结束之后才结束
主进程 等待 守护进程 子进程
守护进程 只守护主进程的代码就可以了
守护线程不行 主线程如果结束了 那么整个进程就结束 所有的线程就能结束
例子
使用多线程实现tcp协议的socket server
开启线程的第二种方式和查看线程ID
from threading import Thread,get_ident class MyThread(Thread): def __init__(self,args): super().__init__() # Thread类的init,在这个方法中做了很多对self的赋值操作,都是给创建线程或者使用线程的时候用的 self.args = args def run(self): print('in my thread : ',get_ident(),self.args) print('main',get_ident()) t = MyThread('wahaha') t.start()
线程中的方法
import time from threading import Thread,get_ident,currentThread,enumerate,activeCount class MyThread(Thread): def __init__(self,args): super().__init__() self.args = args def run(self): time.sleep(0.1) print(currentThread()) print('in my thread : ',get_ident(),self.args) print('main',get_ident()) t = MyThread('wahaha') print(t.is_alive()) t.start() print(activeCount()) # 正在运行的线程的数量 len(enumerate())
main 4572 False 2 <MyThread(Thread-1, started 4924)> in my thread : 4924 wahaha
Thread类
开启线程
传参数
join
和进程的差异
效率
数据共享
守护线程
面向对象的方式开启线程
thread对象的其他方法 : isAlive ,setname,getname
threading模块的方法 : currentTread,activeCount,enumerate
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
在多个进程\线程同时访问一个数据的时候就会产生数据不安全的现象
多进程 访问文件
多线程
同时去访问一个数据
GIL 全局解释器锁
在用一个进程里的每一个线程同一时间只能有一个线程访问CPU
尽量不要设置全局变量
只要在多线程/进程之间用到全局变量 就加上锁
from threading import Lock,Thread noodle = 100 def func(name,lock): global noodle lock.acquire() noodle -= 1 lock.release() print('%s吃到面了'%name) if __name__ == '__main__': lock = Lock() # 线程锁 互斥锁 t_lst = [] for i in range(10): t = Thread(target=func,args=(i,lock)) t.start() t_lst.append(t) for i in t_lst: t.join() print(noodle)
科学家吃面问题
import time from threading import Thread,Lock lock = Lock() noodle_lock = Lock() fork_lock = Lock() def eat1(name): noodle_lock.acquire() print('%s拿到了面'% name) fork_lock.acquire() print('%s拿到了叉子'% name) print('%s在吃面'%name) time.sleep(0.5) fork_lock.release() # 0.01 noodle_lock.release() # 0.01 def eat2(name): fork_lock.acquire() # 0.01 print('%s拿到了叉子' % name) # 0.01 noodle_lock.acquire() print('%s拿到了面'% name) print('%s在吃面'%name) time.sleep(0.5) noodle_lock.release() fork_lock.release() eat_list = ['alex','wusir','太白','yuan'] for name in eat_list: # 8个子线程 7个线程 3个线程eat1,4个线程eat2 Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
以上方法会卡住,由此引出递归锁
递归锁
from threading import RLock rlock = RLock() rlock.acquire() print(1) rlock.acquire() print(2) rlock.acquire() print(3)
递归锁解决死锁问题
import time from threading import Thread,RLock lock = RLock() def eat1(name): lock.acquire() print('%s拿到了面'% name) lock.acquire() print('%s拿到了叉子'% name) print('%s在吃面'% name) time.sleep(0.5) lock.release() lock.release() def eat2(name): lock.acquire() # 0.01 print('%s拿到了叉子' % name) # 0.01 lock.acquire() print('%s拿到了面' %name) print('%s在吃面' %name) time.sleep(0.5) lock.release() lock.release() eat_lst = ['alex','wusir','太白','yuan'] for name in eat_lst: # 8个子线程 7个线程 3个线程eat1,4个线程eat2 Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
互斥锁解决死锁问题
import time from threading import Thread,Lock lock = Lock() def eat1(name): lock.acquire() print('%s拿到了面' % name) print('%s拿到了叉子' % name) print('%s在吃面'%name) time.sleep(0.5) lock.release() # 0.01 def eat2(name): lock.acquire() # 0.01 print('%s拿到了叉子' % name) # 0.01 print('%s拿到了面' % name) print('%s在吃面'%name) time.sleep(0.5) lock.release() eat_lst = ['alex','wusir','太白','yuan'] for name in eat_lst: # 8个子线程 7个线程 3个线程eat1,4个线程eat2 Thread(target=eat1,args=(name,)).start() Thread(target=eat2,args=(name,)).start()
死锁
多把锁同时应用在多个线程中
互斥锁和递归锁哪个好
递归锁 快速恢复服务
死锁问题的出现 是程序的设计或者逻辑的问题
还应该进一步的排除和重构逻辑来保证使用互斥锁也不会发生死锁
互斥锁和递归锁的区别
互斥锁 就是在一个线程中不能连续多次acquire
递归锁 可以在同一个线程中acquire任意次,注意acquire多少次就需要release多少次
信号量
锁 + 计数器
import time from multiprocessing import Semaphore,Process,Pool def ktv1(sem,i): sem.acquire() i +=1 sem.release() def ktv2(i): i += 1 if __name__ == '__main__': sem = Semaphore(5) start = time.time() p_l = [] for i in range(100): p = Process(target=ktv1,args=(sem,i)) p.start() p_l.append(p) for p in p_l: p.join() print('###',time.time() - start) start = time.time() p = Pool(5) p_l = [] for i in range(100): ret = p.apply_async(func=ktv2,args=(sem,i)) p_l.append(ret) p.close() p.join() print('***',time.time() - start)
池 和 信号量
池 效率高
池子里有几个一共就起几个
不管多少任务 池子的个数是固定的
开启进程和关闭进程这些事都是需要固定的开销
就不产生额外的时间开销
且进程池中的进程数控制得好,那么操作系统的压力也小
信号量
有多少个任务就起多少个进程/线程
可以帮助你减少操作系统切换的负担
但是并不能帮助你减少进/线程开启和关闭的时间
事件
wait
等到事件内部的信号变成True就不阻塞了
set
设置信号变成True
clear
设置信号变成False
is_set
查看信号是否为True
数据库连接
import time import random from threading import Event,Thread def check(e): '''检测一下数据库的网络和我的网络是否通''' print('正在检测两台机器之间的网络情况 ...') time.sleep(random.randint(1,3)) e.set() def connet_db(e): e.wait() print('连接数据库 ... ') print('连接数据库成功~~~') e = Event() Thread(target=connet_db,args=(e,)).start() Thread(target=check,args=(e,)).start() import time import random from threading import Event,Thread def check(e): '''检测一下数据库的网络和我的网络是否通''' print('正在检测两台机器之间的网络情况 ...') time.sleep(random.randint(0,2)) e.set() def connet_db(e): n = 0 while n < 3: if e.is_set(): break else: e.wait(0.5) n += 1 if n == 3: raise TimeoutError print('连接数据库 ... ') print('连接数据库成功~~~') e = Event() Thread(target=connet_db,args=(e,)).start() Thread(target=check,args=(e,)).start()
from threading import Condition acquire release wait 阻塞 notify 让wait解除阻塞的工具 wait还是notify在执行这两个方法的前后 必须执行acquire和release from threading import Condition,Thread def func(con,i): con.acquire() # 判断某条件 con.wait() print('threading : ',i) con.release() con = Condition() for i in range(20): Thread(target=func,args=(con,i)).start() con.acquire() # 帮助wait的子线程处理某个数据直到满足条件 con.notify_all() con.release() while True: num = int(input('num >>>')) con.acquire() con.notify(num) con.release()
from threading import Timer def func(): print('执行我啦') # interval 时间间隔 Timer(0.2,func).start() # 定时器 # 创建线程的时候,就规定它多久之后去执行