每日测验
你们之间的差距:每天下课之后以及放假时间
平时多利用截图功能,将可能遗忘或者稍微难理解的知识点截图并加上自己的文件见解,然后保存到自己的手机相册中
昨日内容回顾
current_process().pid # 查看当前进程号 os.getpid() # 查看当前进程号 os.getppid() # 查看当前进程的父进程号 “”” windows终端命令 tasklist tasklist |findstr PID mac终端命令 ps aux ps aux|grep PID “””
-<a name="0a1bb6e3"></a>### 僵尸进程与孤儿进程```python"""僵尸进程:进程结束后不会立刻释放占用的资源(PID),会保留一段时间共父进程查看孤儿进程:子进程存活,父进程意外死亡,孤儿进程操作系统会自动回收相应资源"""
- 
守护进程
"""被守护进程结束之后守护进程立刻也跟着结束"""# 如何开启 在start语句之前写以下代码即可p.daemon = Truep.start()
 - 
互斥锁
```python “”” 多个人在操作同一份数据的时候会出现数据错乱的问题 针对上述问题我们通常都是加锁处理
 
作用: 将并发变成串行,牺牲了程序运行的效率但是保证了数据的安全
注意: 只在操作数据的部分加锁即可 锁尽量不要自己去处理 很容易造成死锁现象
扩展:行锁 表锁 操作表格中的一行数据的时候其他人都不能操作 操作一张表的时候其他人都不能操作 “”” from multiprocessing import Lock mutex = Lock()
抢锁
mutex.acquire()
释放锁
mutex.release()
模拟抢票
-<a name="4c41127a"></a>### 队列Queue```python"""队列:先进先出堆栈:先进后出"""from multiprocessing import Queueq = Queue() # 括号内可以放数字来限制队列的大小q.put() # 放数据 当队列满了再放 阻塞q.get() # 取数据 当队列空了再取 阻塞q.full() # 判断队列是否满了q.empty() # 判断队列是否空了q.get_nowait() # 取数据的时候如果没有数据直接报错q.get(timeout=5) # 取数据的时候如果没有数据等5s还没有则直接报错
本地测试的时候才可能会用到Queue,实际生产用的都是别人封装好的功能非常强大的工具 redis kafka RQ “””
-<a name="e6daf9e1"></a>### 生产者与消费者模型```python"""生产者 + 消息队列 + 消费者为何要有消息队列的存在 是为了解决供需不平衡的问题"""# JoinableQueue"""可以被等待的q你在往队列中放数据的时候 内部有一个计数器自动加1你在从队列中取数据的时候 调用task_done() 内部计时器自动减1q.join() 当计数器为0的时候才继续往下运行"""
每一个进程肯定都自带一个线程
同一个进程内可以创建多个线程 “””
“””
开进程
    申请内存空间
    ”拷贝代码“
    消耗资源较大
开线程
    同一个进程内创建多个线程 无需上述两部操作,消耗资源相对较小
智商 情商 搜商 “””
<a name="c7a1dd9a"></a># 今日内容概要- 开启线程的两种方式- TCP服务端实现并发的效果- 线程对象的join方法- 线程间数据共享- 线程对象属性及其他方法- 守护线程- 线程互斥锁- GIL全局解释器锁- 多进程与多线程的实际应用场景<a name="69f4f6b4"></a># 今日内容详细<a name="7357350b"></a>### 开启线程的两种方式```python# from multiprocessing import Process# from threading import Thread# import time### def task(name):# print('%s is running'%name)# time.sleep(1)# print('%s is over'%name)### # 开启线程不需要在main下面执行代码 直接书写就可以# # 但是我们还是习惯性的将启动命令写在main下面# t = Thread(target=task,args=('egon',))# # p = Process(target=task,args=('jason',))# # p.start()# t.start() # 创建线程的开销非常小 几乎是代码一执行线程就已经创建了# print('主')from threading import Threadimport timeclass MyThead(Thread):def __init__(self, name):"""针对刷个下划线开头双下滑线结尾(__init__)的方法 统一读成 双下init"""# 重写了别人的方法 又不知道别人的方法里有啥 你就调用父类的方法super().__init__()self.name = namedef run(self):print('%s is running'%self.name)time.sleep(1)print('egon DSB')if __name__ == '__main__':t = MyThead('egon')t.start()print('主')
TCP服务端实现并发的效果
import socketfrom threading import Threadfrom multiprocessing import Process"""服务端1.要有固定的IP和PORT2.24小时不间断提供服务3.能够支持并发从现在开始要养成一个看源码的习惯我们前期要立志称为拷贝忍者 卡卡西 不需要有任何的创新等你拷贝到一定程度了 就可以开发自己的思想了"""server =socket.socket() # 括号内不加参数默认就是TCP协议server.bind(('127.0.0.1',8080))server.listen(5)# 将服务的代码单独封装成一个函数def talk(conn):# 通信循环while True:try:data = conn.recv(1024)# 针对mac linux 客户端断开链接后if len(data) == 0: breakprint(data.decode('utf-8'))conn.send(data.upper())except ConnectionResetError as e:print(e)breakconn.close()# 链接循环while True:conn, addr = server.accept() # 接客# 叫其他人来服务客户# t = Thread(target=talk,args=(conn,))t = Process(target=talk,args=(conn,))t.start()"""客户端"""import socketclient = socket.socket()client.connect(('127.0.0.1',8080))while True:client.send(b'hello world')data = client.recv(1024)print(data.decode('utf-8'))
线程对象的join方法
from threading import Threadimport timedef task(name):print('%s is running'%name)time.sleep(3)print('%s is over'%name)if __name__ == '__main__':t = Thread(target=task,args=('egon',))t.start()t.join() # 主线程等待子线程运行结束再执行print('主')
同一个进程下的多个线程数据是共享的
from threading import Threadimport timemoney = 100def task():global moneymoney = 666print(money)if __name__ == '__main__':t = Thread(target=task)t.start()t.join()print(money)
线程对象属性及其他方法
from threading import Thread, active_count, current_threadimport os,timedef task(n):# print('hello world',os.getpid())print('hello world',current_thread().name)time.sleep(n)if __name__ == '__main__':t = Thread(target=task,args=(1,))t1 = Thread(target=task,args=(2,))t.start()t1.start()t.join()print('主',active_count()) # 统计当前正在活跃的线程数# print('主',os.getpid())# print('主',current_thread().name) # 获取线程名字
守护线程
# from threading import Thread# import time### def task(name):# print('%s is running'%name)# time.sleep(1)# print('%s is over'%name)### if __name__ == '__main__':# t = Thread(target=task,args=('egon',))# t.daemon = True# t.start()# print('主')"""主线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束因为主线程的结束意味着所在的进程的结束"""# 稍微有一点迷惑性的例子from threading import Threadimport timedef foo():print(123)time.sleep(1)print('end123')def func():print(456)time.sleep(3)print('end456')if __name__ == '__main__':t1 = Thread(target=foo)t2 = Thread(target=func)t1.daemon = Truet1.start()t2.start()print('主.......')
线程互斥锁
from threading import Thread,Lockimport timemoney = 100mutex = Lock()def task():global moneymutex.acquire()tmp = moneytime.sleep(0.1)money = tmp - 1mutex.release()if __name__ == '__main__':t_list = []for i in range(100):t = Thread(target=task)t.start()t_list.append(t)for t in t_list:t.join()print(money)
GIL全局解释器锁
Ps:博客园密码:xiaoyuanqujing@666
"""In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiplenative threads from executing Python bytecodes at once. This lock is necessary mainlybecause CPython’s memory management is not thread-safe. (However, since the GILexists, other features have grown to depend on the guarantees that it enforces.)""""""python解释器其实有多个版本CpythonJpythonPypypython但是普遍使用的都是CPython解释器在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行同一个进程下的多个线程无法利用多核优势!!!疑问:python的多线程是不是一点用都没有???无法利用多核优势因为cpython中的内存管理不是线程安全的内存管理(垃圾回收机制)1.应用计数2.标记清楚3.分代回收""""""重点:1.GIL不是python的特点而是CPython解释器的特点2.GIL是保证解释器级别的数据的安全3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******)4.针对不同的数据还是需要加不同的锁处理5.解释型语言的通病:同一个进程下多个线程无法利用多核优势"""
GIL与普通互斥锁的区别
from threading import Thread,Lockimport timemutex = Lock()money = 100def task():global money# with mutex:# tmp = money# time.sleep(0.1)# money = tmp -1mutex.acquire()tmp = moneytime.sleep(0.1) # 只要你进入IO了 GIL会自动释放money = tmp - 1mutex.release()if __name__ == '__main__':t_list = []for i in range(100):t = Thread(target=task)t.start()t_list.append(t)for t in t_list:t.join()print(money)"""100个线程起起来之后 要先去抢GIL我进入io GIL自动释放 但是我手上还有一个自己的互斥锁其他线程虽然抢到了GIL但是抢不到互斥锁最终GIL还是回到你的手上 你去操作数据"""
同一个进程下的多线程无法利用多核优势,是不是就没有用了
"""多线程是否有用要看具体情况单核:四个任务(IO密集型\计算密集型)多核:四个任务(IO密集型\计算密集型)"""# 计算密集型 每个任务都需要10s单核(不用考虑了)多进程:额外的消耗资源多线程:介绍开销多核多进程:总耗时 10+多线程:总耗时 40+# IO密集型多核多进程:相对浪费资源多线程:更加节省资源
代码验证
# 计算密集型# from multiprocessing import Process# from threading import Thread# import os,time### def work():# res = 0# for i in range(10000000):# res *= i## if __name__ == '__main__':# l = []# print(os.cpu_count()) # 获取当前计算机CPU个数# start_time = time.time()# for i in range(12):# p = Process(target=work) # 1.4679949283599854# t = Thread(target=work) # 5.698534250259399# t.start()# # p.start()# # l.append(p)# l.append(t)# for p in l:# p.join()# print(time.time()-start_time)# IO密集型from multiprocessing import Processfrom threading import Threadimport os,timedef work():time.sleep(2)if __name__ == '__main__':l = []print(os.cpu_count()) # 获取当前计算机CPU个数start_time = time.time()for i in range(4000):# p = Process(target=work) # 21.149890184402466t = Thread(target=work) # 3.007986068725586t.start()# p.start()# l.append(p)l.append(t)for p in l:p.join()print(time.time()-start_time)
总结
"""多进程和多线程都有各自的优势并且我们后面在写项目的时候通常可以多进程下面再开设多线程这样的话既可以利用多核也可以介绍资源消耗"""
作业
- 整理并发编程三天内容理论,用自己的概述
 - 需要掌握如何开设进程和如何开设线程的代码
 - 利用多进程或多线程自己实现TCP服务端的并发
 - 整理python基础阶段知识点及项目代码,ATM购物车,选课系统一定要自己脱稿从头到位敲出来
 - 预习并发编程剩余知识点,参考博客小猿取经
 
