python网络编程(4)—— 多任务、多线程
python网络编程(4)—— 多任务
介绍
多件事情同时运行,即多任务。
在我们的任务管理器中所看到的多个进程同时运行就是多任务情形。
有顺序的进行任务不是多任务,如先唱歌在跳舞。
from time import sleep def sing(): for i in range(3): print(f'正在唱歌。。。{i}') sleep(1) def dance(): for i in range(3): print(f'正在跳舞。。。{i}') sleep(1) if __name__ == '__main__': sing() dance()
让唱歌跳舞同时进行,所用的方法就是多任务。
import threading from time import sleep def sing(): for i in range(3): print(f'正在唱歌。。。{i}') sleep(1) def dance(): for i in range(3): print(f'正在跳舞。。。{i}') sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start()
在计算机中,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
我们平时看到的多任务实际上是cpu在不停地切换执行程序。
注意:
- 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
- 并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的。
多线程
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
为了提高程序的执行效率,多线程就成了必要。
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用
以下是顺序运行的代码,
import time def saySorry(): print('亲爱的我错了我可以吃饭了吗?') time.sleep(1) if __name__ == '__main__': start = time.time() saySorry() end = time.time() print(f'代码执行耗时{end-start}') 执行结果: 亲爱的我错了我可以吃饭了吗? 代码执行耗时1.0002381801605225
以下是多线程代码:
import threading import time def saySorry(): print('亲爱的我错了我可以吃饭了吗?') time.sleep(1) if __name__ == '__main__': start = time.time() for i in range(5): t = threading.Thread(target=saySorry) t.start() end = time.time() print(f'代码执行耗时{end-start}') 执行结果: 亲爱的我错了我可以吃饭了吗? 亲爱的我错了我可以吃饭了吗? 亲爱的我错了我可以吃饭了吗? 亲爱的我错了我可以吃饭了吗? 亲爱的我错了我可以吃饭了吗? 代码执行耗时0.0010030269622802734
可以明显看出使用了多线程并发的操作,花费时间要短很多
当调用
start()时,才会真正的创建线程,并且开始执行
关于线程执行顺序
import threading from time import sleep,ctime def sing(): print(f'开始唱歌。。。{ctime()}') sleep(3) print(f'结束唱歌。。。{ctime()}') def dance(): print(f'开始跳舞。。。{ctime()}') sleep(3) print(f'结束跳舞。。。{ctime()}') if __name__ == '__main__': print(f'程序开始。。。{ctime()}') t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() while True: print(threading.enumerate()) print(ctime()) if len(threading.enumerate())<=1: break sleep(0.5) t1.join() t2.join() print(f'程序结束。。。{ctime()}')
当我们执行多次多线程程序时,可以看出,多线程程序的执行顺序是不确定的。当我们执行到
sleep()的时候,线程进入阻塞状态,sleep结束后,线程进入就绪状态,等待调度。而线程调度会自主选择一个线程执行,直到所有线程全部执行完成。但线程的执行顺序我们是无法控制的。
线程之间共享全局变量
线程无参数:
g_num = 100 def work1(): global g_num for i in range(10): g_num += 1 print(f'-----------------in work1 g_num={g_num}') def work2(): print(f'-----------------in work1 g_num={g_num}') if __name__ == '__main__': print(f'程序开始时,g_num的初始值是{g_num}') t1 = threading.Thread(target=work1) t1.start() t1.join() t2 = threading.Thread(target=work2) t2.start() t2.join()
线程有参数:
def work1(lis): lis.append(33) lis.append(44) print(f'-----------------in work1 lis={lis}') def work2(lis): print(f'-----------------in work1 lis={lis}') if __name__ == '__main__': lis = [11,22] print(f'程序开始时,lis的初始值是{lis}') # 如果线程任务需要接收参数 # 创建线程时,以元组的方式传给args即可 t1 = threading.Thread(target=work1,args=(lis,)) t1.start() t1.join() t2 = threading.Thread(target=work2,args=(lis,)) t2.start() # 线程之间共享全局变量,包括可变和不可变类型
注意
线程之间共享全局变量会出现资源紧张的问题。
g_num = 0 def work1(num): global g_num for i in range(num): g_num += 1 print(f'-----------------in work2 g_num={g_num}') def work2(num): global g_num for i in range(num): g_num += 1 print(f'-----------------in work1 g_num={g_num}') if __name__ == '__main__': print(f'程序开始时,g_num的初始值是{g_num}') t1 = threading.Thread(target=work1,args=(1000000,)) t2 = threading.Thread(target=work2,args=(1000000,)) t1.start() t2.start() t1.join() t2.join() print(f'程序结束时,g_num的最终值是{g_num}')
结果按照正常来说应该是200 0000,但实际执行结果却是:
程序开始时,g_num的初始值是0
-----------------in work2 g_num=999886
-----------------in work1 g_num=1145184
程序结束时,g_num的最终值是1145184
原因:前面说过,线程执行是无序的,这导致了资源竞争。当线程一拿到数据时本应往下传递,但在传递的时候,线程二还没拿到线程一的数据,由于线程一不再占用资源,于是线程二就开始执行了,导致线程一执行结果即正在传递的数据失效作废,但所有线程还在往下执行。
这种现象是概率型的,但事件越多概率越大,也就导致了以上执行结果与预想结果偏差过大。
所以说,线程间是不安全的。
互斥锁
为了解决多个线程间共享数据的资源竞争问题,python引进互斥锁。
g_num = 0 mutex = threading.Lock() # 创建锁对象 def work1(num): global g_num for i in range(num): mutex.acquire() # 上锁 g_num += 1 mutex.release() # 解锁 print(f'-----------------in work2 g_num={g_num}') def work2(num): global g_num for i in range(num): mutex.acquire() # 上锁 g_num += 1 mutex.release() # 解锁 print(f'-----------------in work1 g_num={g_num}') if __name__ == '__main__': print(f'程序开始时,g_num的初始值是{g_num}') t1 = threading.Thread(target=work1,args=(1000000,)) t2 = threading.Thread(target=work2,args=(1000000,)) t1.start() t2.start() t1.join() t2.join() print(f'程序结束时,g_num的最终值是{g_num}')
缺陷:效率变低。可能产生死锁。
死锁代码:
m1 = threading.Lock() m2 = threading.Lock() def run1(): # 对m1加锁 m1.acquire() print('run------------------1') time.sleep(1) m2.acquire() print('run------------------2') time.sleep(1) m2.release() # 对m1解锁 m1.release() def run2(): # 对m2加锁 m2.acquire() print('run------------------2') time.sleep(1) m1.acquire() print('run------------------1') time.sleep(1) m1.release() # 对m2解锁 m2.release()
解决死锁办法:
1、银行家算法
注意事项
- join() —— 主线程会等待该县城结束后才会结束。
- 并行、并发、同步、异步、互斥、阻塞是多线程必须了解的概念。
- Python实现简单多线程任务队列
- python tk/ttk制作 安卓群控助手,多台设备多任务多线程执行
- 【Python】使用 multiprocessing.dummy 执行多线程任务
- python 多线程网络编程 ( 二 )
- Python实现简单多线程任务队列
- 【Python】使用 multiprocessing.dummy 执行多线程任务
- Python学习:11、多任务--多线程
- python 用map()函数创建多线程任务
- python并行处理任务时 该用多进程?还是该用多线程?
- python 从入门到精通——多任务、多线程编程
- python网络编程socket之多线程
- 【原创】编写多线程Python爬虫来过滤八戒网上的发布任务
- Python网络编程基于多线程实现多用户全双工聊天功能示例
- python单线程与多线程执行任务对比
- Python 30 行代码实现小型多线程任务队列
- Python学习笔记(十三)——保持时间、计划任务和启动程序以及多线程
- PHP + PYTHON 多任务多线程,后台运行,计划任务-实现方法
- python多线程处理重复任务10000次
- 基于python多线程实现Linux任务并发执行
- 使用TheadPoolExecutor线程池进行python多线程任务执行