Python高级 多任务 线程
一、 多任务引入
1. 多任务
a> 多任务
概念: 多个任务同时执行,就是多任务
windows软件、程序: 多任务的操作系统程序
自己编写的python程序:
1. 能同时执行多个任务: 多任务程序
1. 不能同时执行多个任务: 单任务程序
作用:
1. 提高了多任务的执行效率
2. 更充分利用了cpu
b> 并行与并发 : 表现方式
并行:
当任务数小于或者等于cpu核数时,每一个任务都有对应的cpu来处理执行,即任务真的是一起执行的
并发:
指的是任务数多于cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
2. 模拟在线听歌程序
- 下载音乐任务函数
- 播放音乐任务函数
import time
def download_music():
"""模拟下载音乐,需要6秒"""
for i in range(6):
time.sleep(1) # 休眠1秒,模拟耗时的操作1秒
print("--正在下载音乐...--", i)
def play_music():
"""模拟播放音乐,需要6秒"""
for i in range(6):
time.sleep(1) # 休眠1秒,模拟耗时的操作1秒
print("--正在播放音乐...--", i)
if name == 'main':
# 1. 下载音乐任务
download_music()
# 2. 播放音乐任务
play_music()
二、多线程实现多任务(重点)
1. 对线程的理解(难点)
1.一个程序运行起来至少有一个进程,一个进程至少有一个线程
2.处理器cpu分配给线程,即cpu真正运行的是线程中的代码
3.分配cpu给线程时,是通过时间片轮训方式进行的
4.进程是操作系统分配程序执行资源的单位,而线程是进程的一个实体,
是CPU调度和分配的单位。
2. 两种创建多线程的方式(重点)
a> 创建函数并且传入Thread对象中
"""创建函数并且传入Thread 对象中
步骤分析:
1. 导入模块 threading
2. 准备函数,封装任务
3. 由threading模块的Thread类创建线程
指定一下新创建的线程要执行的代码
4. 执行新创建的线程
"""
import time
import threading
def download_music():
"""模拟下载音乐,需要6秒"""
for i in range(6):
time.sleep(1) # 休眠1秒,模拟耗时的操作1秒
print("--正在下载音乐...--", i)
def play_music():
"""模拟播放音乐,需要6秒"""
for i in range(6):
time.sleep(1) # 休眠1秒,模拟耗时的操作1秒
print("--正在播放音乐...--", i)
if name == 'main':
# 由threading模块的Thread类创建线程 ,指定一下新创建的线程要执行的代码
# target:目标,指向,表示新创建的线程要执行的代码在target指向的函数中,注意: download_music不要加()
t1=threading.Thread(target=download_music) # ctrl+p :查看方法的参数
t2=threading.Thread(target=play_music)
# 执行线程
t1.start()
t2.start()
b> 创建Thread类子类,将要执行的代码 写到run函数里面
1. 导入模块threading
2. 编写类,继承threading.Thread类
3. 重写run方法:
把子线程要执行的代码放在run方法中
4. 由自定义的线程类创建在线程对象
5. 执行子线程
import threading
import time
class MyThread(threading.Thread):
def run(self):
"""把线程要执行的代码写到该方法中"""
# print("run----", threading.current_thread().name)
print("run----", self.name)
for i in range(3):
time.sleep(1)
print("--run--代码块%d"%i)
def main():
# 由自定义的线程类创建在线程对象
t1 = MyThread() # ctrl+alt+v : 生成局部变量
# 执行子线程
t1.start() # start内部会调用run方法
if name == 'main':
#threading.current_thread().name 取得当前执行该代码的线程
print("main----",threading.current_thread().name)
main()
三、线程认识加强
1. 子线程何时开启、何时结束
""" 子线程何时开启、何时结束
1.子线程何时开启,何时执行子线程中的代码
在执行了子线程的start方法后,才真正开启线程,并且立即执行子线程中的代码
2.子线程何时结束
当执行完代码,立即结束
3.主线程何时结束
主线程等待所有的子线程执行完,再 结束
4.thread.join()与threading.enumerate()
- threading.enumerate(): 枚举当前的线程情况,返回的是列表
- thread.join():
thread:线程对象,t1
thread.join() : 阻塞当前执行t1.join() 语句的线程(阻塞主线程),等到t1子线程执行完毕后,再解阻塞(主线程),执行后续的代码
"""
import threading
import time
def work1():
"""任务1"""
for i in range(3):
time.sleep(1) # 休眠1秒,模拟耗时的操作是一秒
print('--work1--代码块%d,当前线程情况:%s' %(i,len(threading.enumerate())))
def work2():
"""任务2"""
for i in range(3):
time.sleep(1) # 休眠1秒,模拟耗时的操作是一秒
print('--work2--代码块%d,当前线程情况:%s' % (i, len(threading.enumerate())))
def main():
print("子线程创建前的线程情况:",threading.enumerate())
# 通过threading.Thread创建子线程
t1=threading.Thread(target=work1)
t2=threading.Thread(target=work2)
# time.sleep(0.5) # 确保子线程创建完毕
print("子线程创建后的线程情况:", threading.enumerate())
# 执行子线程
t1.start()
t2.start()
t2.join()#阻塞当前执行t2.join() 语句的线程(主线程),等到t2子线程执行完毕后,再解阻塞(主线程),再执行后续的代码
print("主线程最后执行的代码")
if name == 'main':
main()
2. 线程的执行顺序
线程执行是无序的
import threading
import time
class MyThread(threading.Thread):
def run(self):
"""子线程要执行的代码封装在该方法中"""
for i in range(3):
time.sleep(1)
print("代码块%d,当前线程的名称:%s"%(i,self.name))
def task():
for i in range(3):
time.sleep(1)
print("代码块%d,当前线程的名称:%s" % (i, threading.current_thread().name))
if name == 'main':
# 通过自定义的线程类来创建对线程
for i in range(10):
t1=MyThread()
t1.start()
# 主线程运行
task()
四、多线程-共享全局变量
1. 共享全局变量
import threading
g_num = 100 # 全局变量
def work1():
global g_num # 声明使用全局变量
g_num += 1
print("--work1的g_nums=%d" % g_num)
def work2():
global g_num # 声明使用全局变量
g_num += 1
print("--work2的g_nums=%d" % g_num)
def work3():
print("--work3的g_nums=%d" % g_num)
if __name__ == '__main__':
# 创建子线程
t1=threading.Thread(target=work1)
t1.start()
t1.join() # 阻塞主线程, 当t1线程执行完,再解阻塞,执行后续的代码
t2=threading.Thread(target=work2)
t2.start()
t2.join()
# 主线程执行
work3()
2. 列表当作实参传递到线程中
import threading
def work1(nums):
"""
任务1, 在列表参数中添加'work1'字符串
:param nums: 列表类型
"""
nums.append('work1')
# g_list.append('work1')
print("--work1---nums=%s"%nums)
# print("--work1---g_list=%s"%g_list)
def work2(nums):
"""
任务2, 在列表参数中添加'work2'字符串
:param nums: 列表类型
"""
nums.append('work2')
# print("--work2---nums=%s"%nums)
print("--work2---g_list=%s" % g_list)
g_list=[11,22] # 全局变量, 列表类型
def main():
# 创建线程
# target: 目标,执行,表示新创建的线程要执行的代码在target指向的函数中
# args=(g_list,) 传递的参数是传给target指向的函数
t1=threading.Thread(target=work1,args=(g_list,))
t1.start()
t1.join()
t2=threading.Thread(target=work2,args=(g_list,))
t2.start()
t2.join()
if name == 'main':
main()
五、多线程-共享全局变量-问题
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确,即会遇到线程安全问题
import threading
g_nums=0 # 全局变量
def task1(loop):
"""任务1:loop循环变量"""
global g_nums
for i in range(loop):
# g_nums+=1
temp=g_nums
g_nums=temp+1
print("--task1--g_nums=%d"%g_nums)
def task2(loop):
"""任务2:loop循环变量"""
global g_nums
for i in range(loop):
# g_nums+=1
temp=g_nums
g_nums=temp+1
print("--task2--g_nums=%d"%g_nums)
if name == 'main':
# 创建线程1
t1=threading.Thread(target=task1,args=(1000000,))
t2=threading.Thread(target=task2,args=(1000000,))
t1.start()
t2.start()
t1.join()
t2.join()
print("最后的结果:g_nums=%d"%g_nums)
六、同步的概念
同步(synchronize)
就是指协同步调,按预定的先后次序进行运行。比如:你说完,我再说;你做完,我再做;你执行完,我再执行。
在多线程编程中,一些敏感数据不允许被多个线程同时访问,因为会出现线程安全问题,通过线程同步机制,能保证共享数据在任何时刻,最多有一个线程访问,以保证数据的正确性
线程同步,就是线程的等待
七、互斥锁
同步是一种思想,一种机制
-
互斥锁是实现同步机制的一种方案 ,好比多线程是实现多任务的一种方案
-
使用互斥锁
-
取得一把互斥锁
mutexLock=threading.Lock() -
上锁
mutexLock.acquire()- 解锁,释放锁
mutexLock.release()
- 解锁,释放锁
import threading
g_nums = 0 # 全局变量
mutexLock = threading.Lock() # 取得锁
def task1(loop):
"""任务1:loop循环变量""" global g_nums for i in range(loop): # g_nums+=1 # 上锁 mutexLock.acquire() temp = g_nums g_nums = temp + 1 # 释放锁,解锁 mutexLock.release() print("--task1--g_nums=%d" % g_nums)
def task2(loop):
"""任务2:loop循环变量""" global g_nums for i in range(loop): # 上锁 mutexLock.acquire() temp = g_nums g_nums = temp + 1 # 释放锁,解锁 mutexLock.release() print("--task2--g_nums=%d" % g_nums)
if name == ‘main’:
# 创建线程1 t1 = threading.Thread(target=task1, args=(1000000,)) t2 = threading.Thread(target=task2, args=(1000000,)) t1.start() t2.start() t1.join() t2.join() print("最后的结果:g_nums=%d" % g_nums)
-
八、多任务版UDP聊天器
要求:
1.通过threading.Thread创建一个子线程,执行recv_msg的函数,用于接收对方发送过来的消息
2.在主线程中调用send_msg函数,采集用户输入的聊天数据并且发送给对方
"""
多任务版UDP聊天器
要求:
1.通过threading.Thread创建一个子线程,执行recv_msg的函数,用于接收对方发送过来的消息
2.在主线程中调用send_msg函数,采集用户输入的聊天数据并且发送给对方
"""
import socket
import threading
def send_msg(udp_socket):
"""发送消息"""
while True:
send_data = input("请输入要发送的消息:")
dest_addr = ('192.168.110.117', 8080)
# 编码
data = send_data.encode('utf-8')
udp_socket.sendto(data, dest_addr)
def recv_msg(udp_socket):
"""接收消息"""
while True:
# 等待接收目标主机回传的消息
recv_data = udp_socket.recvfrom(1024)
# 解码
data = recv_data[0].decode('gbk')
# 显示接收的数据
print(data)
def main():
# 1. 创建udp socket对象
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.绑定端口
udp_socket.bind(('', 9999))
# 3. 通过threading.Thread创建一个子线程,执行recv_msg的函数,用于接收对方发送过来的消息
t1=threading.Thread(target=recv_msg,args=(udp_socket,))
t1.start()
# 4. 在主线程中调用send_msg函数,采集用户输入的聊天数据并且发送给对方
send_msg(udp_socket)
# 5.关闭socket
# udp_socket.close()
if name == 'main':
main()