Python多线程阐述

应用场景

  • 写了一个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两种方法解决线程冲突问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值