并发和并行、进程和线程
线程:Thread类、线程的启动、线程的传参
threading的属性和方法、Thread实例的属性和方法
start()和run()的区别、多线程
daemon线程和join方法
并发和并行
- 并发(concurrent)
处理多个任务的能力,强调一个时间段内 - 并行(parallel)
同时处理多个任务的能力,强调同一时刻互不干扰做
进一步理解可参看全局解释器锁GIL
并发的解决方案
- 队列、缓冲区
- 争抢
- 预处理,缓存cache
- 并行
- 提速
- 消息中间件:如RabbitMQ、ActiveMQ(Apache)、RocketMQ(阿里Apache)、kafka(Apache)等。
进程和线程
- 在实现了线程的操作系统中,线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个程序的执行实例就是一个进程
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
- 程序是源代码编译后的文件,而这些文件存放在磁盘上。当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器
线程的状态
- 就绪(Ready)
- 运行(Running)
- 阻塞(Blocked)
- 终止(Terminated)
python中的进程和线程
- 进程会启动一个解释器进程,线程共享一个解释器进程
- 进程靠线程执行代码,至少有一个主线程,其它线程是工作线程
- 主线程是第一个启动的线程
- 父线程:如果线程A中启动了一个线程B,A就是B的父线程
- 子线程:B就是A的子线程
线程
threading.Thread类
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
先熟悉如下几个参数
- target:线程调用的对象,就是目标函数
- name:为线程起的名字
- args:为目标函数传递实参,元组
- kwargs:为目标函数关键字传参,字典
线程启动start()
import threading
import time
def work():
print("i'm working~~~~~")
time.sleep(1)
print('finished')
t = threading.Thread(target=work, name="work") # 线程对象
t.start() # 启动,可注释掉本行查看效果发现如果不start,线程并不能启动
线程退出
- Python没有提供线程退出的方法
- 线程退出
- 线程函数内语句执行完毕
- 线程函数中抛出未处理的异常
import threading
import time
def work():
count = 0
while True:
count += 1
time.sleep(1)
print("i'm working~~~~~")
if count > 5:
# raise RuntimeError(count)
break
print('finished')
t = threading.Thread(target=work, name="work")
t.start()
# 1/0
# raise RuntimeError('error')
# import sys
# sys.exit(100)
Python的线程没有优先级、没有线程组的概念,也不能被销毁、停止、挂起,那也就没有恢复、中断了
主线程退出了,工作线程也能继续工作
线程传参及线程相关属性和方法
- args和kwargs传参:args元组,kwargs字典,本质就是函数传参
- threading的属性和方法
- current_thread():返回当前线程对象
- main_thread():返回主线程对象
- active_count():当前处于alive状态的线程个数
- enumerate():返回所有alive的线程的列表,不包括已经终止的线程和未开始的线程
- get_ident():返回当前线程的ID,非0整数
- threading.Thread实例的属性和方法
- name:只是个标识,名称可以重名,也可用getName()获取、setName()设置
- ident:线程ID,它是非0整数。线程启动后才会有ID,否则为None。线程退出,此ID依旧可以访问,此ID可以重复使用
- is_alive():返回线程是否活着
import threading
import time
def show_thread_info():
print('current thread:', threading.current_thread())
print('main thread:', threading.main_thread())
print('active thread count:', threading.active_count())
print('current thread id:', threading.get_ident())
print('all alive thread:', threading.enumerate())
def work(name: str, count, *, num):
show_thread_info()
times = 0
while True:
if times > num:
break
time.sleep(1)
print(f'{name} is working')
times += 1
print('finished')
t = threading.Thread(target=work, name="work", args=('jerry', 10), kwargs=dict(num=3)) # t即Thread实例 kwargs={'num': 3}
t.start()
print(1, t.getName())
t.setName('jerry')
print(2, t.name)
# show_thread_info() # 在主线程中执行该函数,对比不同
while True:
time.sleep(0.5)
print('{} {} {}'.format(t.name, t.ident, 'alive' if t.is_alive() else 'dead'))
# if not t.is_alive():
# print(f'{t.name} restart')
# t.start() # 线程死了后不能再次重启 RuntimeError: threads can only be started once
if not t.is_alive():
break
start()和run()的本质区别
import threading
import time
def work():
print(threading.enumerate())
print("i'm working~~~~~")
time.sleep(1)
print('finished')
class MyThread(threading.Thread):
def start(self):
print('start~~~~~')
super().start()
def run(self):
print('run~~~~~')
super().run() # 只注释掉这行试试,并不能执行work()
t = MyThread(target=work, name='work')
# t.run() # 执行run()看起来像是执行了work(),但这其实只是普通的函数调用,并没有创建新的线程
t.start() # 先执行start启动一个新的线程,再执行run,将target函数运行该新的线程上
使用start方法启动线程,启动了一个新的线程,名字叫做work运行。但是使用run方法的,并没有启动新的线程,就是在主线程中调用了一个普通的函数而已。因此,启动线程需要使用start方法,且对于这个线程来说,start方法只能调用一次。(设置
_started
属性实现(可查看源码))
多线程
多线程,多个线程,一个进程中如果有多个线程运行,就是多线程,实现一种并发
- 一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程
- 一个进程至少有一个主线程,其他线程称为工作线程
import threading
import time
def work():
t = threading.current_thread()
for i in range(5):
time.sleep(1)
print(f"{t.name} {t.ident} is working~~~~~")
print(f'{t.name} fnished')
class MyThread(threading.Thread):
def start(self):
super().start()
def run(self):
super().run()
t1 = MyThread(target=work, name='work1')
t2 = MyThread(target=work, name='work2')
t1.start()
t2.start()
daemon线程
# Thread中__init__关于daemon源码:
def __init__(self, group=None, target=None, name=None,args=(), kwargs=None, *, daemon=None):
.....
if daemon is not None: # 如果daemon不为None则用用户设定的daemon值(True/False)
self._daemonic = daemon
else:
self._daemonic = current_thread().daemon # 如果为None则取当前线程的daemon值
.....
线程daemon属性,如果设定就是用户的设置,否则就取当前线程的daemon值
- python中的daemon不是Linux中的守护进程
- 构造线程的时候,可以设置daemon属性,这个属性必须在start方法前设置好
- 主线程是non-daemon线程,即daemon == False
相关属性和方法
- daemon:表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常
- isDaemon():是否是daemon线程
- setDaemon:设置为daemon线程,必须在start方法之前设置
# 例1: daemon=None默认值
import threading
import time
def work():
current = threading.current_thread()
time.sleep(1)
print(f"{current.name} {current.ident} is working~~~~~")
print(f'work thread is_daemon:{current.isDaemon()}')
t = threading.Thread(target=work, name='work', daemon=None) # None为默认值
t.start()
print(f'main thread is_daemon: {threading.current_thread().isDaemon()}') # threading.current_thread().daemon
print("end~~~~~")
-------输出结果-----------
main thread is_daemon: False
end~~~~~
work 123145584820224 is working~~~~~
work thread is_daemon:False
例2: daemon为False
import threading
import time
def work():
current = threading.current_thread()
time.sleep(1)
print(f"{current.name} {current.ident} is working~~~~~")
print(f'work thread is_daemon:{current.isDaemon()}')
t = threading.Thread(target=work, name='work', daemon=False)
t.start()
print(f'main thread is_daemon: {threading.current_thread().isDaemon()}')
print("end~~~~~")
------输出结果(同例1)---------------------
main thread is_daemon: False
end~~~~~
work 123145391276032 is working~~~~~
work thread is_daemon:False
例3:daemon为True
import threading
import time
def work():
current = threading.current_thread()
print(current)
time.sleep(1)
print(f"{current.name} {current.ident} is working~~~~~")
print(f'work thread is_daemon:{current.isDaemon()}')
t = threading.Thread(target=work, name='work')
print(1, t.daemon)
t.setDaemon(True)
print(2, t.daemon)
t.start()
print(f'main thread is_daemon: {threading.current_thread().isDaemon()}')
time.sleep(0.5)
print("end~~~~~")
----输出结果:主线程空闲后查还在执行的工作线程work为daemon线程,直接结束-------------
1 False
2 True
<Thread(work, started daemon 123145559367680)>
main thread is_daemon: False
end~~~~~
结论:
- 线程具有一个daemon属性,可以手动设置为True或False,也可以不设置,则取默认值None
- 如果不设置daemon,就取当前线程的daemon来设置它
- 主线程是non-daemon线程,即daemon = False
- 从主线程创建的所有线程的不设置daemon属性,则默认都是daemon = False
- Python程序在没有活着的non-daemon线程运行时,程序才退出,也就是除主线程之外剩下的只能都是daemon线程,主线程才能退出,否则主线程就只能等待
【我要退出了,还有non-daemon的吗?有就等会,没有就退出了】
# 执行如下例子,进一步理解daemon的参数的意义
import time
import threading
def worker(name, timeout):
time.sleep(timeout)
print('{} working'.format(name))
# 主线程 是non-daemon线程
t1 = threading.Thread(target=worker, args=('t1', 5), daemon=True) # 调换5和10看看效果
t1.start()
t2 = threading.Thread(target=worker, args=('t2', 10), daemon=False)
t2.start()
print('Main Thread Exits')
join方法
join(timeout=None),是线程的标准方法之一
import time
import threading
def worker(name, timeout):
time.sleep(timeout)
print('{} working'.format(name))
t = threading.Thread(target=worker, args=('t', 3), daemon=True)
t.start()
t.join() # 看看有join和没join的区别
print('Main Thread Exits')
daemon线程执行完了,主线程才退出了
import time
import threading
def worker(name, timeout):
time.sleep(timeout)
print('{} working'.format(name))
t = threading.Thread(target=worker, args=('t', 3), daemon=True)
t.start()
t.join(timeout=1)
print('~~~~~~~~~')
t.join(timeout=1) # 延长timeout再试试
print('Main Thread Exits')
- 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止
- 一个线程可以被join多次
- timeout参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束
- 在哪(如主线程中main)调用谁(工作线程t)的join方法,main就要等t
【join的线程哥俩好】
daemon线程应用场景
- 后台任务。如发送心跳包、监控,这种场景最多
- 主线程工作才有用的线程。如主线程中维护这公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
- 随时可以被终止的线程
import time
import threading
def worker2(name):
while True:
time.sleep(1)
print('{} working'.format(name), threading.current_thread().isDaemon())
def worker1(name):
current = threading.current_thread()
print("{}'s daemon = {}".format(name, current.isDaemon()))
t2 = threading.Thread(target=worker2, args=('t2',))
t2.start()
# t2.join() # (2)也可设置主线程等t1, t1等t2的来让t2一直运行
t1 = threading.Thread(target=worker1, args=('t1',), daemon=True) # 使t2一直运行的最简单方式是设置t1.daemon为默认值及None/False
t1.start()
# t1.join() # (1)这里join的话只是主线程等t1,但t1很快就结束了,不能达到t2一直运行
time.sleep(4)
print('Main Thread Exits')
- 如果果主线程退出,想所有其它工作线程一起退出,就使用daemon=True来创建工作线程
- 如果在non-daemon线程A中,对另一个daemon线程B使用了join方法,这个线程B设置成daemon就没有什么意义了,因为non-daemon线程A总是要等待B。
- 如果在一个daemon线程C中,对另一个daemon线程D使用了join方法,只能说明C要等待D,主线程退出,C和D不管是否结束,也不管它们谁等谁,都要被杀掉
参考
- magedu