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过程总结:

  1. 子线程添加锁的参数,业务代码执行完成后执行锁的释放操作,lock.release()
  2. 主线程后执行时,定义一个锁列表,根据线程数量,生成一个锁的列表
  3. 根据线程数量,初始化锁,并设置锁的状态为锁定,初始化锁:_thread.allocate_lock(),锁定:lock.acquire()
  4. 添加锁至锁的列表
  5. 执行子线程
  6. 主线程最后添加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使用过程:

  1. 主线程中进行子线程创建,threading.Thread(target = threadname, args = paramlist)
  2. 子线程创建完成后添加至定义的线程列表中
  3. for in启动所有创建的字线程,thread.start()
  4. 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’: 语句代表直接执行当前文件才会执行的代码,被调用时则不会执行该代码块