46.Python开启多个线程
文章目录0.GIL全局解释器锁1.线程2.模块2.1Thread类2.2参数2.3属性2.4绑定方法3.开启线程的两种方式3.1方式13.2方式二3.3速度比较4.数据共享5.守护线程6.互斥锁7.信号量8.Event事件0.GIL全局解释器锁Python在设计之处就考虑到要在主循环中,同时只有一个线程在执行。虽然Python解释器中可以 运行 多个线程,但在任意时刻只有只个线程在解释器中运行。G
·
文章目录
1. GIL全局解释器锁
Python在设计之处就考虑到要在主循环中, 同时只有一个线程在执行.
虽然Python解释器中可以运行多个线程, 但在任意时刻只有一个线程在解释器中运行.
GIL锁控制在同一个时刻只有一个线程在运行.
* Python中的多线程是假的多线程, 即使100个线程跑在100核CPU上, 也只能用到1个核.
2. 线程
为什么会出现线程?
进程的缺点:
1. 非常消耗资源,计算机不能无限开启子进程.
2. 如果开了过多的进程,cpu的切换进程的模式下是非常耗时的.
因为进程的缺点,线程的出现就是为了解决进程的缺点,线程的开销小于进程.
线程: 是轻量级的进程, 一个进程里面至少有一个线程, 线程就是具体干活的, 执行任务的.
线程比进程更加容易创建和撤销, 创建一个线程比创建一个进程要快10-100倍,
多个线程的运行也是在多个线程之间的快速切换, 一个线程可以访问另一个线程的内存地址.
PS:进程相当于一座工厂,线程相当于干活的人
PS:进程是一个资源的实体单位,而cpu操作的最小单位是线程.
2. 开启多线程
开启线程是使用在进程申请的内存空间.
2.1 模块
multiprocessing模块完全模仿了 threading模块的接口, 线程与进程的使用大致相似.
2.2 Thread类
Thread()是创建线程的类, 由该类实例化得到线程对象.
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
参数介绍:
target: 表示调用对象, 子线程要执行的任务.
args: 表示调用对象的位置参数(元组).
kwargs: 表示调用对象的关键字参数(字典).
name: 表示子进程的名称.
2.3 属性
.setDaemon 默认值为False,设置为True, 设置子线程为守护线程,
父线进程结束, 子线程也结束, 需要在.start前设置.
.is_alive() 查看进程是否存活
.getName() 查看进程名称
.setName() 设置进程名称
2.4 绑定方法
.start() 启动线程, 内部调用线程对象的.run()方法.
.run() 进程启动时运行的方法, 正是它去调用target指定的函数, 自定义的内中必须实现该方法.
.terminate() 强制终止线程, 不会进行任何清理.
.isalive() 查看线程是否存活, 返回值为布尔值.
.join() 主进程等待线程的结束.
3. 开启线程的两种方式
线程可以不用在 if __name__ == '__mian__': 下执行.
3.1 方式1
* 1. 定义子线程调用的函数
* 2. 创建子线程对象, 绑定调用的函数.
* 3. 启动子线程
from threading import Thread
# 子线程调用的函数
def task():
print('我是线程')
# 创建子线程对象
t = Thread(target=task)
t.start()
print('我是主进程')
终端显示:
我是线程
我是主线程
_________________________________________________________________________
可能显现在一行: 我是线程我是主线程 因为线程执行速度快, 两个代码抢占终端的使用.
3.2方式2
* 1. 继承Thread类, 自定义线程类, 调用父类的初始化方法, 生成对象.
* 2. 将线程需要执行的代码写在run()方法中.
* 3. 调用自定义类, 创建子线程对象.
* 4. 启动子线程.
from threading import Thread
class Task(Thread):
def __init__(self):
super().__init__()
def run(self):
print('我是进程')
t = Task()
t.start()
print('我是主线程')
终端显示:
我是线程
我是主线程
3.3 速度比较
线程与进程的速度比较.
# 线程
from threading import Thread
import time
# 定义线程调用的函数
def task():
print('我是线程')
# 时间戳
ctime = time.time()
# 创建线程对象
t = Thread(target=task)
# 启动线程
t.start()
# 等待子进程运行结束再往后运行
t.join()
print('此次运行时间%0.8s秒' % (time.time() - ctime))
终端显示: (皮秒级别)
我是线程
此次运行时间0.000633秒
# 进程
from multiprocessing import Process
import time
# 子线程调用的函数
def task():
print('我是进程')
if __name__ == '__main__':
# 获取时间戳
ctime = time.time()
# 创建子进程对象
p = Process(target=task)
# 启动子进程
p.start()
# 等待子进程运行结束再往后运行
p.join()
print('此次运行时间%0.8s秒' % (time.time() - ctime))
终端显示: (微秒级别)
我是子进程
此次运行时间0.061532秒
对象结果: 创建到销毁, 线程比进程要快100倍.
4. 线程之间数据共享
4.1 获取pid
在主进程下开启多个线程, 每个线程中获取pid进程标识符与主进程一样.
from threading import Thread
import os
# 线程1调用的函数
def task1():
print(f'线程1, 获取pid为{os.getpid()}')
# 线程2调用的函数
def task2():
print(f'线程2, 获取pid为{os.getpid()}')
# 线程调用函数列表
task_list = [task1, task2]
# 创建线程对象
for task in thread_list:
t = Thread(target=task)
t.start()
print(f'主进程, 获取pid为{os.getpid()}')
终端显示:
线程1, 获取pid为16716
线程2, 获取pid为16716
主进程, 获取pid为16716
4.2 线程中修改数据
from threading import Thread
# 在全局名称空间中定义一个变量
x = 0
# 定义线程中调用的函数
def task():
global x
x = 10
t = Thread(target=task)
t.start()
# 等待线程执行完毕
t.join()
print(x)
终端显示:
10
_______________________________
所有线程与主进程, 共用一个名称空间.
5. 守护线程
.setDaemon(True), 主进程执行完毕, 立刻销毁子进程.在.start()前设置.
from threading import Thread
import time
# 线程调用的函数
def task():
print(1111)
# 延时3秒
time.sleep(3)
print(3333)
# 创基线程对象
t = Thread(target=task)
# 设置守护进程
t.setDaemon(True)
# 启动线程
t.start()
print(2222)
终端显示:
1111
2222
_________________________
当主进行结束, 线程马上停止.
6. 互斥锁
并发速度太快了, 同时使用共享资源会出问题.
6.1 模拟抢票(不上锁)
在项目目录下新建 db.txt, 并写入数据:
{"count": 1}
from threading import Thread
import time, json
# 查票
def search(i):
ticket = json.load(open('db.txt'))
print('%s查看车票剩余:%s张!' % (i, ticket['count']))
return ticket
# 抢票
def rob(ticket, i):
if ticket['count'] > 0:
ticket['count'] -= 1
time.sleep(0.2) # 网络延时
json.dump(ticket, open('db.txt', 'w'))
print('%s抢票成功' % i)
else:
print('%s没有抢到票' % i)
# 合并到一个函数中
def func(i):
ticket = search(i)
rob(ticket, i)
if __name__ == '__main__':
# 生成5个子进程模拟抢票
for i in range(5):
t = Thread(target=func, args=(i,))
t.start()
终端显示:
0查看车票剩余:1张!
1查看车票剩余:1张!
2查看车票剩余:1张!
4查看车票剩余:1张!
3查看车票剩余:1张!
3抢票成功
4抢票成功
1抢票成功
2抢票成功
0抢票成功
______________________________
一张车票所有子线程都抢到了...
6.2 模拟抢票(上锁)
Python的GIL锁保证同一个时刻只有一个线程在执行, 锁的目的是为了保护共享的数据,
同一个时间只有一个线程来修改数据, 保护不同的数据因该加上不同的锁.
GIL 和 Lock 是两把不同的锁, 保护不一样的数据, 比如垃圾回收,
后者是保护自己开发的应用程序的数据, GIL锁不负责这个事, 只能自定义加锁处理.
GIL是解释器级别的, 保护解释器级别的数据, 所有线程抢GIL锁, 抢的就是执行的权限.
在线程中使用Lock互斥锁, lock_obj.acquire() 为上锁, lock_obj.release() 释放锁.
from threading import Thread, Lock
import time, json
# 查票
def search(i):
ticket = json.load(open('db.txt'))
print('%s查看车票剩余:%s张!' % (i, ticket['count']))
return ticket
# 抢票
def rob(ticket, i):
if ticket['count'] > 0:
ticket['count'] -= 1
time.sleep(0.2) # 网络延时
json.dump(ticket, open('db.txt', 'w'))
print('%s抢票成功' % i)
else:
print('%s没有抢到票' % i)
# 合并到一个函数中
def func(i, lock):
# 使用锁
mutex .acquire()
ticket = search(i)
rob(ticket, i)
# 释放锁
mutex .release()
if __name__ == '__main__':
# 生成锁对象
mutex = Lock()
# 生成5个子进程模拟抢票
for i in range(5):
t = Thread(target=func, args=(i, mutex ))
t.start()
7. 信号量
import time
from threading import Thread, Semaphore
def task(i, locks):
# 加信号量锁
locks.acquire()
print('%s号程序开始执行!' % i)
time.sleep(2)
print('%s号程序执行完毕!' % i)
time.sleep(2)
# 释放信号量锁
locks.release()
# 生成两把锁
locks = Semaphore(2)
for i in range(10):
# 开启10个线程, 每次两个线程抢锁执行
t = Thread(target=task, args=(i, locks))
t.start()
终端显示:
0号程序开始执行!
1号程序开始执行!
1号程序执行完毕!
0号程序执行完毕!
2号程序开始执行!3号程序开始执行!
2号程序执行完毕!3号程序执行完毕!
4号程序开始执行!
5号程序开始执行!
5号程序执行完毕!4号程序执行完毕!
6号程序开始执行!
7号程序开始执行!
6号程序执行完毕!
7号程序执行完毕!
8号程序开始执行!9号程序开始执行!
8号程序执行完毕!
9号程序执行完毕!
8. Event事件
import time
from threading import Thread, Event, Lock
num = 0
def task1(event):
print('%s 开始准备发红包' % 'kid')
time.sleep(1)
# 红包数量
global num
num = 1
print('%s 已经发红包了' % 'kid')
event.set()
def task2(i, event, lock):
print(f'{i}号同学掏出来手机, 准备抢红包!')
event.wait()
lock.acquire()
# 抢了红包就没了
global num
# 查询红包数量
tem = num
# 还有红包就抢
if num > 0:
num -= 1
print(f'现在还有{tem}个红包, {i} 同学抢到了红包')
else:
print(f'现在还有{tem}个红包, {i} 同学手速慢了!')
lock.release()
event = Event()
t0 = Thread(target=task1, args=(event,))
t0.start()
# 生成一把互斥锁
lock = Lock()
for i in range(5):
t1 = Thread(target=task2, args=(i, event, lock))
t1.start()
终端显示:
kid 开始准备发红包
0号同学掏出来手机, 准备抢红包!
1号同学掏出来手机, 准备抢红包!
2号同学掏出来手机, 准备抢红包!
3号同学掏出来手机, 准备抢红包!
4号同学掏出来手机, 准备抢红包!
kid 已经发红包了
现在还有1个红包, 0 同学抢到了红包
现在还有0个红包, 1 同学手速慢了!
现在还有0个红包, 3 同学手速慢了!
现在还有0个红包, 2 同学手速慢了!
现在还有0个红包, 4 同学手速慢了!
更多推荐
已为社区贡献4条内容
所有评论(0)