应用场景
- 写了一个python脚本的接口,线上需要对这个接口做高并发调用,所以线下需要模拟高并发场景,测试接口性能。
- 缩短运行时间
- 有并发需求的时候需要用到,比如页面同时点击。
名次解释:线程
- 线程是操作系统能够进行运算调度的最小单位。
- 线程被包含在进程中,是进程中实际处理单位。
- 一条线程就是一堆指令集合,一条线程是指进程中一个单一顺序的控制流。
- 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 一个进程只能运行在一个核中,所以如果多线程运行,这些线程都是在一个核中运行。
多线程案例
python提供了多种模块用来支持多线程编程
- thread(在python3中改名为_thread)
- threading
thread和threading模块都可以用来创建和管理线程,而thread模块提供了基本的线程和锁支持,threading提供的是更高级的完全的线程管理,一般应用程序推荐使用更高级的threading模块。
案例1:
执行同一个任务,单线程运行程序花费了6.02283787727356秒,运行顺序上第一个线程执行完了才执行第二个线程的。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import time
from datetime import datetime
def Test(name):
for i in range(3):
print(name, datetime.now())
time.sleep(1)
def main():
Test("one")
Test("two")
if __name__ == '__main__':
start = time.time()
main()
end = time.time()
print("运行程序花费了%s秒" % (end - start))
'''
one 2021-03-19 16:01:11.253621
one 2021-03-19 16:01:12.260874
one 2021-03-19 16:01:13.263720
two 2021-03-19 16:01:14.267094
two 2021-03-19 16:01:15.269936
two 2021-03-19 16:01:16.273119
运行程序花费了6.02283787727356秒
Process finished with exit code 0
'''
多线程运行程序花费了3.024076461791992秒,运行顺序上两个线程一起运行的。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
import time
from datetime import datetime
def Test(name):
for i in range(3):
print(name, datetime.now())
time.sleep(1)
def main():
t1 = threading.Thread(target=Test, args=("one",)) # 调用threading.Thread函数,target参数是要执行的函数,args是要传入的参数,为元组类型
t2 = threading.Thread(target=Test, args=("two",))
t1.start() # 启动线程
t2.start()
t1.join() # 必须等子线程运行完了父进程才可以运行
t2.join()
if __name__ == '__main__':
start = time.time()
main()
end = time.time()
print("运行程序花费了%s秒" % (end - start))
'''
one 2021-03-19 16:03:23.071051
two 2021-03-19 16:03:23.071051
two 2021-03-19 16:03:24.085559
one 2021-03-19 16:03:24.085559
one 2021-03-19 16:03:25.090119
two 2021-03-19 16:03:25.090119
运行程序花费了3.024076461791992秒
Process finished with exit code 0
'''
案例2:
同样,多线程模式下,运行顺序上两个线程一起进行的。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
import time
def mission1(num):
for i in range(0, num):
print("mission1......" + str(i))
time.sleep(1) # 此处要插入间隔,否则看不出多线程的区别
def mission2(num):
for i in range(0, num):
print("mission2......" + str(i))
time.sleep(1)
if __name__ == '__main__':
m1 = threading.Thread(target=mission1, args=(5, )) # target为任务名,args为传递进任务的参数
m2 = threading.Thread(target=mission2, args=(5, ))
m1.start() # 开始任务
m2.start()
'''
mission1......0
mission2......0
mission2......1
mission1......1
mission2......2
mission1......2
mission1......3
mission2......3
mission2......4
mission1......4
Process finished with exit code 0
'''
多线程冲突
考虑这样一种情况:
- 一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
或者这样的:
- 假设你银行卡里有1000块,使用两台机器同时登入这张卡,此时两台机器所识别的这张卡的余额都是1000块,这时候操作机器的两个人同时按下取款1000块,机器就会吐出2000块。
多线程冲突的根本原因
- 多线程的优势在于可以同时运行多个任务,但是当多线程调用同一个函数涉及到共享变量时,会发生多线程冲突。
解决办法
- 线程锁就是保护公共资源,一次只能让一个线程访问。
案例1
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
money = 0
# # 定义一个函数用于存钱
# def addMoney():
#
# global money
# for i in range(1000000):
# money += 1
# print(money)
# 线程冲突情况
# def conflict():
# # 有五个线程,这五个线程同时取用同一个资源
# for _ in range(5):
# t = threading.Thread(target=addMoney)
# t.start()
# pass
# conflict()
"""
1247040
1282304
2705568
2861408
2922634
Process finished with exit code 0
"""
# 给资源加锁
lock = threading.Lock() # 互斥锁
def addMoney2():
global money
# 给公共资源处理过程加锁
if lock.acquire():
for i in range(1000000):
money+=1
print(money)
lock.release() #释放
def threadLock():
for _ in range(5):
t = threading.Thread(target=addMoney2)
t.start()
threadLock()
'''
1000000
2000000
3000000
4000000
5000000
Process finished with exit code 0
'''
案例2
案例2说的是上面的银行卡案例,加锁前:
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
import time
account = 1000
def withdraw(num):
global account
if num <= account:
time.sleep(1) # 给予判断的余地
t = account - num
account = t
print("已取出" + str(num))
else:
print("余额不足")
if __name__ == '__main__':
m1 = threading.Thread(target=withdraw, args=(1000, ))
m2 = threading.Thread(target=withdraw, args=(1000, ))
m1.start()
m2.start()
'''
已取出1000
已取出1000
Process finished with exit code 0
'''
加锁后:
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
import time
account = 1000
def withdraw(num):
global account
lock.acquire() # 加锁
if num <= account:
time.sleep(1) # 给予判断的余地
t = account - num
account = t
print("已取出" + str(num))
else:
print("余额不足")
lock.release() # 解锁
if __name__ == '__main__':
lock = threading.Lock() # 生成锁
m1 = threading.Thread(target=withdraw, args=(1000, ))
m2 = threading.Thread(target=withdraw, args=(1000, ))
m1.start()
m2.start()
'''
已取出1000
余额不足
Process finished with exit code 0
'''
案例3:
- 案例3没有涉及线程冲突,案例3是“多线程案例”中案例1的延伸,对案例1中的多线程上了一个锁,这样当我们的线程1和线程2都同时调用该函数时,必须等线程1执行完了线程2再执行,这样,程序花费的时间就相当于单线程时候的6秒了,也就是上了锁后,多线程变成了单线程,运行效率降低,上锁是解决线程冲突非常不明智的一种做法。
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
@author:
@contact:
@time:
"""
import threading
import time
from datetime import datetime
from threading import Lock
lock = Lock() # 得到一个Lock对象
def Test(name):
with lock:
for i in range(3):
print(name, datetime.now())
time.sleep(1)
def main():
t1 = threading.Thread(target=Test, args=("one",)) # 调用threading.Thread函数,target参数是要执行的函数,args是要传入的参数,为元组类型
t2 = threading.Thread(target=Test, args=("two",))
t1.start() # 启动线程
t2.start()
t1.join() # 必须等子线程运行完了父线程才可以运行
t2.join()
if __name__ == '__main__':
start = time.time()
main()
end = time.time()
print("运行程序花费了%s秒" % (end - start))
'''
one 2021-03-20 15:18:22.841910
one 2021-03-20 15:18:23.862553
one 2021-03-20 15:18:24.874606
two 2021-03-20 15:18:25.881377
two 2021-03-20 15:18:26.883645
two 2021-03-20 15:18:27.887927
运行程序花费了6.061808824539185秒
Process finished with exit code 0
'''
参考资料
Python多线程_thread和Threading
python 使用threading模块解决多线程问题
python两种方法解决线程冲突问题