【Python使用】嘿马python高级进阶全体系教程第6篇:线程执行带有参数的任务,1. 线程执行带有参数的任务的介绍【附代码
本教程的知识点为:操作系统 1. 常见的操作系统 4. 小结 ls命令选项 2. 小结 mkdir和rm命令选项 1. mkdir命令选项 压缩和解压缩命令 1. 压缩格式的介绍 2. tar命令及选项的使用 3. zip和unzip命令及选项的使用 4. 小结 编辑器 vim 1. vim 的介绍 2. vim 的工作模式 3. vim 的末行模式命令 4. vim 的常用命令 获取进程编号 1. 获取进程编号的目的 2. 获取当前进程编号 3. 获取当前父进程编号 4. 小结 线程执行带有参数的任务 1. 线程执行带有参数的任务的介绍 2. args参数的使用 3. kwargs参数的使用 4. 小结 1. s锁的概念 2. s锁示例 3. 避免s锁 4. 小结 端口和端口号的介绍 1. 问题思考 2. 什么是端口 3. 什么端口号 4. 端口和端口号的关系 HTTP 协议 1. HTTP 协议的介绍 2. HTTP 协议的作用 3. 浏览器访问web服务器的通信过程 4. 小结 静态Web服务器-返回固定页面数据 1. 开发自己的静态Web服务器 2. 静态Web服务器-返回固定页面数据的示例代码 3. 小结 静态Web服务器-返回指定页面数据 静态Web服务器-面向对象开发 1. 以面向对象的方式开发静态Web服务器 2. 静态Web服务器-面向对象开发的示例代码 3. 小结 静态Web服务器-命令行启动动态绑定端口号 修改闭包内使用的外部变量 1. 修改闭包内使用的外部变量 2. 小结 装饰器 1. 装饰器的定义 property属性 1. property属性的介绍 2. 装饰器方式 3. 类属性方式 4. 小结 深拷贝和浅拷贝 1. 浅拷贝 2. 深拷贝 3. 浅拷贝和深拷贝的区别 4. 总结 匹配多个字符 1. 匹配多个字符 示例1:* 示例2:+ 示例3:?
完整笔记资料代码->:https://gitee.com/yinuo112/Backend/tree/master/Python/嘿马python高级进阶全体系教程/note.md
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
线程执行带有参数的任务
学习目标
- 能够写出线程执行带有参数的任务
1. 线程执行带有参数的任务的介绍
前面我们使用线程执行的任务是没有参数的,假如我们使用线程执行的任务带有参数,如何给函数传参呢?
Thread类执行任务并给任务传参数有两种方式:
- args 表示以元组的方式给执行任务传参
- kwargs 表示以字典方式给执行任务传参
2. args参数的使用
示例代码:
import threading
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子线程
# args: 以元组的方式给任务传入参数
sub_thread = threading.Thread(target=task, args=(5,))
sub_thread.start()
执行结果:
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成
3. kwargs参数的使用
示例代码:
import threading
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子线程
# kwargs: 表示以字典方式传入参数
sub_thread = threading.Thread(target=task, kwargs={"count": 3})
sub_thread.start()
执行结果:
任务执行中..
任务执行中..
任务执行中..
任务执行完成
4. 小结
-
线程执行任务并传参有两种方式:
-
元组方式传参(args) :元组方式传参一定要和参数的顺序保持一致。
- 字典方式传参(kwargs):字典方式传参字典中的key一定要和参数名保持一致。
线程的注意点
学习目标
- 能够说出线程的注意点
1. 线程的注意点介绍
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
2. 线程之间执行是无序的
import threading
import time
def task():
time.sleep(1)
print("当前线程:", threading.current_thread().name)
if __name__ == '__main__':
for _ in range(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()
执行结果:
当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3
说明:
- 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
- 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
3. 主线程会等待所有的子线程执行结束再结束
假如我们现在创建一个子线程,这个子线程执行完大概需要2.5秒钟,现在让主线程执行1秒钟就退出程序,查看一下执行结果,示例代码如下:
import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
sub_thread = threading.Thread(target=show_info)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")
执行结果:
test: 0
test: 1
over
test: 2
test: 3
test: 4
说明:
通过上面代码的执行结果,我们可以得知: 主线程会等待所有的子线程执行结束再结束
假如我们就让主线程执行1秒钟,子线程就销毁不再执行,那怎么办呢?
- 我们可以设置守护主线程
守护主线程:
- 守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
- threading.Thread(target=show_info, daemon=True)
- 线程对象.setDaemon(True)
设置守护主线程的示例代码:
import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
# 创建子线程守护主线程
# daemon=True 守护主线程
# 守护主线程方式1
sub_thread = threading.Thread(target=show_info, daemon=True)
# 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
# 守护主线程方式2
# sub_thread.setDaemon(True)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")
执行结果:
test: 0
test: 1
over
3. 线程之间共享全局变量
需求:
- 定义一个列表类型的全局变量
- 创建两个子线程分别执行向全局变量添加数据的任务和向全局变量读取数据的任务
- 查看线程之间是否共享全局变量数据
import threading
import time
# 定义全局变量
my_list = list()
# 写入数据任务
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.1)
print("write_data:", my_list)
# 读取数据任务
def read_data():
print("read_data:", my_list)
if __name__ == '__main__':
# 创建写入数据的线程
write_thread = threading.Thread(target=write_data)
# 创建读取数据的线程
read_thread = threading.Thread(target=read_data)
write_thread.start()
# 延时
# time.sleep(1)
# 主线程等待写入线程执行完成以后代码在继续往下执行
write_thread.join()
print("开始读取数据啦")
read_thread.start()
执行结果:
write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]
4. 线程之间共享全局变量数据出现错误问题
需求:
- 定义两个函数,实现循环100万次,每循环一次给全局变量加1
- 创建两个子线程执行对应的两个函数,查看计算后的结果
import threading
# 定义全局变量
g_num = 0
# 循环一次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环一次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 启动线程
second_thread.start()
执行结果:
sum1: 1210949
sum2: 1496035
注意点:
多线程同时对全局变量操作数据发生了错误
错误分析:
两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:
- 在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0
- 然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1
- 然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
- 这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1
全局变量数据错误的解决办法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机
线程同步的方式:
- 线程等待(join)
- 互斥锁
线程等待的示例代码:
import threading
# 定义全局变量
g_num = 0
# 循环1000000次每次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环1000000次每次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
# 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行
first_thread.join()
# 启动线程
second_thread.start()
执行结果:
sum1: 1000000
sum2: 2000000
5. 小结
- 线程执行执行是无序的
- 主线程默认会等待所有子线程执行结束再结束,设置守护主线程的目的是主线程退出子线程销毁。
- 线程之间共享全局变量,好处是可以对全局变量的数据进行共享。
-
线程之间共享全局变量可能会导致数据出现错误问题,可以使用线程同步方式来解决这个问题。
-
线程等待(join)
互斥锁
学习目标
- 能够知道互斥锁的作用
1.互斥锁的概念
互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
注意:
- 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
为了更好的理解互斥锁,请看下面的图:
3. 互斥锁的使用
threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
互斥锁使用步骤:
# 创建锁
mutex = threading.Lock()
# 上锁
mutex.acquire()
...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...
# 释放锁
mutex.release()
注意点:
- acquire和release方法之间的代码同一时刻只能有一个线程去操作
- 如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
4. 使用互斥锁完成2个线程对同一个全局变量各加100万次的操作
import threading
# 定义全局变量
g_num = 0
# 创建全局互斥锁
lock = threading.Lock()
# 循环一次给全局变量加1
def sum_num1():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 释放锁
lock.release()
# 循环一次给全局变量加1
def sum_num2():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
# 释放锁
lock.release()
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
second_thread.start()
# 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待
# 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行
执行结果:
sum1: 1000000
sum2: 2000000
说明:
通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题
5. 小结
- 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
- 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
- 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
- 互斥锁如果没有使用好容易出现死锁的情况
- 点赞
- 收藏
- 关注作者
评论(0)