概念
作用
多线程可以完成多任务
多线程效果图
多线程的使用
导入线程模块
import threading
线程类Thread参数说明
启动线程
start方法
代码示例
# 导入线程模块
import threading
import time
def eat():
# 获取当前线程
current_thread = threading.current_thread()
print("吃饭子线程eat:", current_thread)
for i in range(4):
print("吃饭中....")
time.sleep(0.5)
def drink():
# 获取当前线程
current_thread = threading.current_thread()
print("喝水子线程drink:", current_thread)
for i in range(4):
print("喝水中....")
time.sleep(0.5)
if __name__ == '__main__':
# 获取当前线程
current_thread = threading.current_thread()
print("主线程:", current_thread)
# 创建子线程
eat_thread = threading.Thread(target=eat)
drink_thread = threading.Thread(target=drink)
# 启动子线程
eat_thread.start()
drink_thread.start()
输出
主线程: <_MainThread(MainThread, started 4378697152)>
吃饭子线程eat: <Thread(Thread-1, started 123145390465024)>
吃饭中....
喝水子线程drink: <Thread(Thread-2, started 123145407254528)>
喝水中....
吃饭中....
喝水中....
吃饭中....
喝水中....
吃饭中....
喝水中....
线程执行带有参数的任务
方法1:元祖方式传参
代码示例
import threading
def info_print(name, age):
print("name: %s age: %d" % (name, age))
if __name__ == '__main__':
# 元祖方式传参,要保证元祖里元素顺序和函数的参数顺序一样
sub_thread = threading.Thread(target=info_print, args=("梁来福", 1.1))
sub_thread.start()
输出
name: 梁来福 age: 1
方法2:字典方式传参
代码示例
import threading
def info_print(name, age):
print("name: %s age: %d" % (name, age))
if __name__ == '__main__':
# 元祖方式传参,要保证元祖里元素顺序和函数的参数顺序一样
# sub_thread = threading.Thread(target=info_print, args=("梁来福", 1.1))
# sub_thread.start()
# 字典方式传参,要保证字典里的key和函数参数名保持一致
sub_thread = threading.Thread(target=info_print, kwargs={"age": 1, "name": "梁来福"})
sub_thread.start()
输出
name: 梁来福 age: 1
线程的注意点
注意点1:
线程之间执行是无序的,是由操作系统调度进程来决定的
代码示例
import threading
import time
def task():
time.sleep(2)
print(threading.current_thread())
if __name__ == '__main__':
for i in range(10):
sub_thread = threading.Thread(target=task)
sub_thread.start()
输出
<Thread(Thread-1, started 123145564463104)><Thread(Thread-7, started 123145665200128)>
<Thread(Thread-9, started 123145698779136)>
<Thread(Thread-10, started 123145715568640)>
<Thread(Thread-5, started 123145631621120)>
<Thread(Thread-3, started 123145598042112)>
<Thread(Thread-4, started 123145614831616)><Thread(Thread-6, started 123145648410624)>
<Thread(Thread-2, started 123145581252608)>
<Thread(Thread-8, started 123145681989632)>
注意点2
主线程会等待所有的子线程执行结束再结束
代码问题展示
import threading
import time
def task():
while True:
print("死循环中......")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
sub_thread = threading.Thread(target=task)
sub_thread.start()
time.sleep(1)
print("Over")
exit()
输出
死循环中......
死循环中......
死循环中......
死循环中......
死循环中......
Over
死循环中......
死循环中......
死循环中......
.............................
结论
当运行代码后,程序处于死循环中,会等待子线程执行完...
解决方法:守护主线程
写法1
import threading
import time
def task():
while True:
print("死循环中......")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
# daemon=True表示守护主线程,主线程退出子线程直接销毁
sub_thread = threading.Thread(target=task, daemon=True)
sub_thread.start()
time.sleep(1)
print("Over")
# exit()
输出
死循环中......
死循环中......
死循环中......
死循环中......
死循环中......
Over
写法2
import threading
import time
def task():
while True:
print("死循环中......")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
# daemon=True表示守护主线程,主线程退出子线程直接销毁
# sub_thread = threading.Thread(target=task, daemon=True)
sub_thread = threading.Thread(target=task)
sub_thread.setDaemon(True)
sub_thread.start()
time.sleep(1)
print("Over")
# exit()
输出
死循环中......
死循环中......
死循环中......
死循环中......
死循环中......
Over
注意点3
线程之间共享全局变量
代码示例
import threading
import time
# 定义全局变量
g_list = []
# 添加数据的任务
def add_task():
for i in range(3):
# 每循环一次把数据添加到全局变量列表
g_list.append(i)
print("add:", i)
time.sleep(0.2)
# 代码执行到此说明数据添加完成
print("添加数据完成,总数据为:", g_list)
# 读取数据的任务
def read_task():
print("读取出来的数据:", g_list)
if __name__ == '__main__':
# 创建子线程
add_thread = threading.Thread(target=add_task)
read_thread = threading.Thread(target=read_task)
# 启动线程执行任务
add_thread.start()
# join表示当前线程(主线程)等待子线程(添加数据)执行完成后代码再继续执行
add_thread.join()
read_thread.start()
输出
add: 0
add: 1
add: 2
添加数据完成,总数据为: [0, 1, 2]
读取出来的数据: [0, 1, 2]
说明
该代码展示的是:创建两个子线程(一个写入数据,一个读取数据),join保证当写入数据完成后再进行读取,读取出了写入的数据,证明线程之间共享全局变量
注意点4
线程之间共享全局变量数据出现错误问题
需求
1、定义两个函数,实现分别循环1w次,每循环一次给全局变量加1
2、创建两个子线程分别执行对应的两个函数,查看计算后的结果
代码示例
import threading
g_num = 0
# 循环1w次执行的任务
def task1():
for i in range(10000):
# 每循环一次给全局变量加1
global g_num
g_num += 1 # g_num = g_num + 1
# 代码执行至此,说明数据计算完成
print("task1:", g_num)
def task2():
for i in range(10000):
# 每循环一次给全局变量加1
global g_num
g_num += 1 # g_num = g_num + 1
# 代码执行至此,说明数据计算完成
print("task2:", g_num)
if __name__ == '__main__':
first_thread = threading.Thread(target=task1)
second_thread = threading.Thread(target=task2)
# 启动线程执行任务
first_thread.start()
second_thread.start()
错误分析
解决办法
方法1:线程等待(join)
代码示例
................
if __name__ == '__main__':
first_thread = threading.Thread(target=task1)
second_thread = threading.Thread(target=task2)
# 启动线程执行任务
first_thread.start()
# 线程等待
first_thread.join()
second_thread.start()
输出
task1: 10000
task2: 20000
方法2:互斥锁
概念
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁
如何使用
threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁
使用步骤
# 创建锁
mutex = threading.Lock()
# 上锁
mutex.acquire()
...
这里编写代码能保证同一时刻只能有一个线程去操作,对共享数据进行锁定
...
# 释放锁
mutex.release()
注意点
代码示例
import threading
# 全局变量
g_num = 0
# 创建互斥锁 Lock本质上是一个函数
lock = threading.Lock()
# 循环1w次执行的任务
def task1():
# 上锁
lock.acquire()
for i in range(10000):
# 每循环一次给全局变量加1
global g_num
g_num += 1 # g_num = g_num + 1
# 代码执行至此,说明数据计算完成
print("task1:", g_num)
# 释放锁
lock.release()
def task2():
# 上锁
lock.acquire()
for i in range(10000):
# 每循环一次给全局变量加1
global g_num
g_num += 1 # g_num = g_num + 1
# 代码执行至此,说明数据计算完成
print("task2:", g_num)
# 释放锁
lock.release()
if __name__ == '__main__':
first_thread = threading.Thread(target=task1)
second_thread = threading.Thread(target=task2)
# 启动线程执行任务
first_thread.start()
second_thread.start()
输出
task1: 10000
task2: 20000
总结
- 互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题
- 线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降
死锁
概念
一直等待对方释放锁的情景就是死锁
代码示例
import threading
# 创建互斥锁
lock = threading.Lock()
# 需求:多线程同时根据下标在列表中取值,要保证同一时刻只能又一个线程去取值
def get_value(index):
# 上锁
lock.acquire()
my_list = [1, 11, 111, "梁来福"]
# 判断下标越界
if index >= len(my_list):
print("下标越界,无法取到值:", index)
# 取值不成功,也需要释放互斥锁,不要影响后面的线程去取值
# 锁需要在合适的地方进行释放,防止死锁
lock.release()
return
# 根据下标取值
value = my_list[index]
print(value)
# 释放锁
lock.release()
if __name__ == '__main__':
# 创建10个线程,同时执行根据下标取值的任务
for i in range(10):
# 每循环一次创建一个子线程
sub_thread = threading.Thread(target=get_value, args=(i,))
# 启动线程执行任务
sub_thread.start()
输出
1
11
111
梁来福
下标越界,无法取到值: 4
下标越界,无法取到值: 5
下标越界,无法取到值: 6
下标越界,无法取到值: 7
下标越界,无法取到值: 8
下标越界,无法取到值: 9