详解python中的多线程

Posted 非晚非晚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解python中的多线程相关的知识,希望对你有一定的参考价值。


本文会直接举几个例子来看看python怎么实现多线程。之前在c++的 一篇文章中,对多线程的概念进行了介绍,这里就不继续展开讲解了,有兴趣可以点击链接进行了解。

Python3 通过两个标准库 _thread 和 threading 提供对线程的支持,由于_thread只是为了兼容python2的thread模块,所以推荐使用threading模块实现多线程

1. threading的一些功能介绍

方法说明
threading.currentThread()返回当前的线程变量
threading.enumerate()返回一个包含正在运行的线程的list
threading.activeCount()返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

方法说明
run()用以表示线程活动的方法。
start()启动线程活动。
join([time])等待至线程中止。中止的方式:正常退出或者抛出未处理的异常-或者是可选的超时发生
isAlive()返回线程是否活动的。
getName()返回线程名。第一个子线程名字为:Thread-1,主线程名字为:MainThread
setName()设置线程名。

下列举例说明这些功能,如果看不明白,没关系,可以暂时先跳过,学习完所有内容之后再回头看。

import threading
import time

def thread_1(num):
    for i in range(num):
        #print(threading.currentThread()) #当前线程变量
        time.sleep(0.5)

def thread_2(num):
    for i in range(num):
        #print(threading.currentThread()) #当前线程变量
        time.sleep(0.5)
        
def main():
	#args是函数对应的参数,以元组的形式存在
    t_thread_1 = threading.Thread(target=thread_1, args=(5,))
    t_thread_2 = threading.Thread(target=thread_2, args=(6,))
    print("t_thread_1 isAlive(): ", t_thread_1.isAlive())  # 是否存活,False
    t_thread_1.start()#启动
    t_thread_2.start()#启动

    print("threading.enumerate(): ", threading.enumerate()) #范围一个list
    print("threading.activeCount(): ", threading.activeCount()) #当前3个,包括一个主线程

    #打印某个线程的信息
    print("t_thread_1 isAlive(): ",t_thread_1.isAlive())#是否存活,True
    print("t_thread_1 getName(): ", t_thread_1.getName())  # 获取线程名字Thread-1
    print("t_thread_2 getName(): ", t_thread_2.getName())  # 获取线程名字Thread-2

    t_thread_1.join()
    t_thread_2.join()
    print("t_thread_1 isAlive(): ", t_thread_1.isAlive())  # 是否存活,False
    print("主线程结束")

if __name__ == '__main__':
    main()

2. 线程的实现——函数和类

(1)函数方式实现多线程

import threading
import time

def thread_1(num):
    for i in range(num):
        print("子线程1 :%d" % i)
        time.sleep(0.5)

def thread_2(num):
    for i in range(num):
        print("子线程2 :%d" % i)
        time.sleep(0.5)
        
def main():
	#args是函数对应的参数,以元组的形式存在
    t_thread_1 = threading.Thread(target=thread_1, args=(5,))
    t_thread_2 = threading.Thread(target=thread_2, args=(6,))
    t_thread_1.start()#启动
    t_thread_2.start()#启动

if __name__ == '__main__':
    main()

(2)类的方式实现多线程

步骤:

  1. 导入threading包
  2. 创建一个类继承threading.Thread类,重写run方法
  3. 创建类对象,调用start()方法启动线程
import threading
import time

class MyThread(threading.Thread):
    def run(self): #重写
        for i in range(3):
            time.sleep(1)
            #self.name:线程的名字从Thread-1开始
            msg = "I'm "+self.name+' @ '+str(i)
            print(msg)

def main():
    for i in range(5):#创建5个线程
        t = MyThread()
        t.start()

if __name__ == '__main__':
    main()

3. 守护线程与同步线程

(1)非守护线程——默认线程

非守护线程:当主线程执行完所有的程序后,这个时候主线程并不回退出,也就是不会销毁,直到所有的子线程完成了各自的任务后才自动销毁。默认情况下,python产生的线程是非守护线程

当线程设置为setDaemon(False),则表示线程为非守护线程,默认为False,如果设置为True则为守护线程。

下列代码中,子线程设置了延迟,主线程会等待子线程结束。

import threading
import time

def my_thread():
    time.sleep(2)#延迟2s
    print('---子线程结束---')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.start()
    print('---主线程---结束')

if __name__ == '__main__':
    main()

输出:

---主线程---结束
---子线程结束---

(2)守护线程

守护线程,也就是当主线程结束之后,子线程也会随之消亡。建立守护线程只需要设置setDaemon(True)就可以。

以下列代码为例,因为子线程设置为守护线程,而且子线程中设置了延迟2s,所以主线程结束之后,不会运行子线程的print。

import threading
import time

def my_thread():
    time.sleep(2)
    print('---子线程结束---')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.setDaemon(True)
    t1.start()
    print('---主线程---结束')

if __name__ == '__main__':
    main()

输出:

---主线程---结束

(3)同步线程(join)

当主线程遇到同步线程的时候,即遇到join()的时候会等待(即主线程进入堵塞状态,无法继续前行)所有的同步线程执行完毕,之后主线程才继续往下走。

import threading
import time

def my_thread_1():
    for i in range(2):#运行2s
        print('子线程1睡眠第秒'.format(i + 1))
        time.sleep(1)
    print('子线程1结束!!!')

def my_thread_2():
    for i in range(5):#运行5s
        print('子线程2睡眠第秒'.format(i + 1))
        time.sleep(1)
    print('子线程2结束!!!')

def main():
    t1 = threading.Thread(target=my_thread_1)
    t1.start()

    t2 = threading.Thread(target=my_thread_2)
    t2.start()

    t1.join()#等待子线程1结束
    t2.join()#等待子线程2结束

    print('主线程结束!!!')

if __name__ == '__main__':
    main()

输出:

子线程1睡眠第1秒
子线程2睡眠第1秒
子线程1睡眠第2秒
子线程2睡眠第2秒
子线程1结束!!!
子线程2睡眠第3秒
子线程2睡眠第4秒
子线程2睡眠第5秒
子线程2结束!!!
主线程结束!!!

(4)非守护线程 + 同步线程

一般情况:当我们设置某个子线程为非守护线程并设置其为同步线程的时候,也就是上面所说的同步线程join()是一样的,主线程会等待所有的同步线程执行完毕再往下执行,因为默认情况下,创建的线程为非守护线程

第二种情况:如果在join中添加了timeout参数后,主线程会等待子线程timeout的时间,然后继续往下执行主线程执行完后不会退出,而是等待子线程完成任务后再退出。详情可以见下一节。

import threading
import time

def my_thread():
    for i in range(2):
        print('子线程1睡眠第秒'.format(i + 1))
        time.sleep(1)
    print('子线程结束!!!')

def main():
    t1 = threading.Thread(target=my_thread)
    t1.setDaemon(False)
    t1.start()
    t1.join()
    #t1.join(timeout=1)#join设置timeout,单位秒
    print('主线程结束!!!')

if __name__ == '__main__':
    main()

输出:

子线程1睡眠第1秒
子线程1睡眠第2秒
子线程结束!!!
主线程结束!!!

(5)守护线程 + 同步线程join

一般情况:如果是守护线程这种情况下,join不设置timeout的话,主线程会等待子线程完成任务再继续往下执行

第二种情况如果join设置了timeout参数的话,等待子线程的时间后,主线程继续往下进行,主线程结束了自己的任务后,发现子线程还是个守护线程,但是这时候子线程还没完成自己的任务,而主线程也不会等待它,而是直接自动销毁

import threading
import time

def thread1():
    for i in range(5):
        print('子线程睡眠第秒'.format(i + 1))
        time.sleep(1)
    print('子线程结束!!!')

def main():
    t1 = threading.Thread(target=thread1)
    t1.setDaemon(True)
    t1.start()
    t1.join(timeout=1)#设置timeout,单位秒
    print('主线程结束!!!')

if __name__ == '__main__':
    main()

输出:

子线程睡眠第1秒
主线程结束!!!

4. 互斥锁

由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们因此也称为“线程不安全”。为了防止上面情况的发生,就出现了互斥锁(Lock)。

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, my_time):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.my_time = my_time
    def run(self):
        print ("开启线程: " + self.name)
        # 获取锁,用于线程同步
        threadLock.acquire()
        print_time(self.name, self.my_time, 5)#每个运行5次
        # 释放锁,开启下一个线程
        threadLock.release()

def print_time(threadName, my_time, counter):
    while counter:
        time.sleep(my_time)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
threads = []

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)#1s打印一次
thread2 = myThread(2, "Thread-2", 2)#2s打印一次

# 开启新线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

输出:

开启线程: Thread-1
开启线程: Thread-2
Thread-1: Wed Dec 29 14:41:14 2021
Thread-1: Wed Dec 29 14:41:15 2021
Thread-1: Wed Dec 29 14:41:16 2021
Thread-1: Wed Dec 29 14:41:17 2021
Thread-1: Wed Dec 29 14:41:18 2021
Thread-2: Wed Dec 29 14:41:20 2021
Thread-2: Wed Dec 29 14:41:22 2021
Thread-2: Wed Dec 29 14:41:24 2021
Thread-2: Wed Dec 29 14:41:26 2021
Thread-2: Wed Dec 29 14:41:28 2021
退出主线程

以上是关于详解python中的多线程的主要内容,如果未能解决你的问题,请参考以下文章

Python中的多线程如何正确运用?案例详解

Python中的多线程如何正确运用?案例详解

Python多线程编程详解

python多进程实例详解

Java中的多线程技术全面详解

Java中的多线程技术全面详解