python学习笔记-13. python的多线程
文章目录
- python学习笔记-13. python的多线程
- 前言
- 一、python与线程
- 二、_thread的简单使用
- 三、threading简单使用
- 四、threading继承使用
- 总结
前言
- 进程的概念:
进程时执行中程序,拥有独立地址空间、内存、数据栈,由操作系统管理,进程通讯IPC方式共享信息 - 线程的概念:
同进程下执行,共享相同的上下文,线程间的信息共享和通讯会比较容易,多线程可以并发执行,需要同步原语
一、python与线程
- 解释器主循环
- 主循环中只有一个控制线程在执行
- 使用全局解释器锁(GIL)
GIL保证只有一个线程
- 设置GIL
- 切换进一个线程去运行
- 执行其中一个操作:1.指定数量的字节码指令 2.线程主动让出控制权
- 把线程设置回睡眠状态(切换出现线程)
- 解锁GIL
- 重复以上步骤
python两种线程管理方式:
- _thread: 提供基本的线程和锁
- threading:提供更高级别功能更全面的线程管理,支持同步机制和守护进程
二、_thread的简单使用
_thread注意点:
- 主线程退出时所有线程都会退出
import _thread
import logging
from time import ctime, sleep
# logging 初始化
logging.basicConfig(level=logging.INFO)
# list为每个线程的等待时间
list_thread = [2, 4]
def test_thread(nthread, nsec, lock):
logging.info("start test_thread " + str(nthread) + " at " + ctime())
sleep(nsec)
logging.info("end test_thread " + str(nthread) + " at " + ctime())
lock.release()
def main():
logging.info("start Allthread at " + ctime())
# 定义锁的列表,用来存放所有的锁
locks = []
nthreads = range(len(list_thread))
# 根据线程数量,生成锁列表
for i in nthreads:
# 生成一个锁
lock = _thread.allocate_lock()
# 修改锁的状态为锁定
lock.acquire()
# 锁列表添加锁
locks.append(lock)
# 循环启动线程,start_new_thread函数,第一个参数为函数名,后面为函数的参数
for i in nthreads:
_thread.start_new_thread(test_thread, (i, list_thread[i], locks[i]))
# 遍历线程,判断锁是否释放,如果没有释放就继续循环,确保不会因为主线程结束倒是子线程未执行完成
for i in nthreads:
while locks[i].locked(): pass
logging.info("end Allthread at " + ctime())
if __name__ == '__main__':
main()
_thread过程总结:
- 子线程添加锁的参数,业务代码执行完成后执行锁的释放操作,lock.release()
- 主线程后执行时,定义一个锁列表,根据线程数量,生成一个锁的列表
- 根据线程数量,初始化锁,并设置锁的状态为锁定,初始化锁:_thread.allocate_lock(),锁定:lock.acquire()
- 添加锁至锁的列表
- 执行子线程
- 主线程最后添加while循环,检查锁状态,如果存在未释放的锁就一直循环,确保每个子线程执行完成
三、threading简单使用
_thread的使用还是有点麻烦的,threading则不用去操作锁,相对比较简单一点
import _thread
import logging
import threading
from time import ctime, sleep
# logging 初始化
logging.basicConfig(level=logging.INFO)
# list为每个线程的等待时间
list_thread = [2, 4]
def test_thread(nthread, nsec):
logging.info("start test_thread " + str(nthread) + " at " + ctime())
sleep(nsec)
logging.info("end test_thread " + str(nthread) + " at " + ctime())
def main():
logging.info("start Allthread at " + ctime())
nthreads = range(len(list_thread))
threads = []
# 定义一个线程列表
# 循环启动线程,使用threading.Thread()方法,第一个参数target为函数名,第二个args为函数的参数
for i in nthreads:
t = threading.Thread(target=test_thread, args=(i, list_thread[i]))
# 线程创建完成后添加线程至线程组
threads.append(t)
# 遍历线程,启动线程,thrading线程创建完成后并不会直接启动,需要调用start方法进行启动
for i in nthreads:
threads[i].start()
# 使用join方法,进行确保子线程执行结束才能退出
for i in nthreads:
threads[i].join()
logging.info("end Allthread at " + ctime())
if __name__ == '__main__':
main()
threading使用过程:
- 主线程中进行子线程创建,threading.Thread(target = threadname, args = paramlist)
- 子线程创建完成后添加至定义的线程列表中
- for in启动所有创建的字线程,thread.start()
- for in 监控所有子线程,确保执行完成才能结束,否则就线程阻塞等待,thread.join()
四、threading继承使用
上面的_thread和threading直接调用是为了方便理解,更多时使用直接继承threading或Thread来使用,后面尽量使用继承的方法来写
import _thread
import logging
import threading
from time import ctime, sleep
# logging 初始化
logging.basicConfig(level=logging.INFO)
# list为每个线程的等待时间
list_thread = [2, 4]
# 定义线程类继承Thread
class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.func = func
self.args = args
self.name = name
# 重写run方法
def run(self):
self.func(*self.args)
# 子线程执行代码块
def test_thread(nthread, nsec):
logging.info("start test_thread " + str(nthread) + " at " + ctime())
sleep(nsec)
logging.info("end test_thread " + str(nthread) + " at " + ctime())
def main():
logging.info("start Allthread at " + ctime())
nthreads = range(len(list_thread))
threads = []
# 定义一个线程列表
# 循环启动线程,使用自定义的Mythread,调用构造函数,传入函数名,参数,线程名
for i in nthreads:
t = MyThread(test_thread, (i, list_thread[i]), test_thread.__name__)
# 线程创建完成后添加线程至线程组
threads.append(t)
# 遍历线程,启动线程,thrading线程创建完成后并不会直接启动,需要调用start方法进行启动
for i in nthreads:
threads[i].start()
# 使用join方法,进行确保子线程执行结束才能退出
for i in nthreads:
threads[i].join()
logging.info("end Allthread at " + ctime())
if __name__ == '__main__':
main()
总结
没什么用的小技巧:
python的__name__ = ‘main’: python中__name__ 是内置属性,当直接运行一个.py文件时,它的__name__属性是__main__,但是当当前文件被其他模块调用时,当前文件的__name__属性就变成了它的文件名,而非时__main__,所以if name == ‘main’: 语句代表直接执行当前文件才会执行的代码,被调用时则不会执行该代码块