首页 > 编程语言 >Python多线程编程——threading模块

Python多线程编程——threading模块

时间:2022-09-19 17:34:16浏览次数:96  
标签:__ Thread Python self threading 线程 time 多线程

本文参考:
https://blog.csdn.net/youngwyj/article/details/124720041
https://blog.csdn.net/youngwyj/article/details/124833126

目录

前言

在日常的开发中经常会用到多线程和多进程编程,使用多线程编程可降低程序的复杂度,使程序更简洁高效。

线程是程序执行流的最小单元,是进程的一个实体,一个进程可以拥有多个线程,多个线程可以共享进程所拥有的资源。

线程可以提升程序的整体性能,一般分为内核线程和用户线程,内核线程由操作系统内核创建和撤销,用户线程不需要内核支持,是在用户程序中实现的线程。

需要注意的是:线程不能独立运行,他必须依附于进程;线程可以被抢占(中断)和暂时搁置(休眠)。

threading模块

1.简介

在 python3 中有两种实现多线程编程的模块。

  • 1._thread模块。python2中使用了thread模块实现,而 python3 不再使用该模块,为实现兼容在 python3 中将该模块改为了 _thread 模块。
  • 2.threading模块。因为 _thread 模块提供的是一个简单低级的线程和锁,但 threading 模块是基于 Java 的线程模块设计,相较于 _thread 更高级,所以本文只讲解 threading 模块。

其实python并不适合做多线程的开发,因为 python 解释器使用了内部的全局解释器锁(GIL锁),使得在无论何时计算机只会允许在处理器上运行单个线程,即便多核GPU也是如此(即GIL锁同一时刻只允许一个进程只有一个线程被CPU调度),所以它的效率较其他语言低。

threading 模块创建线程的方式与 Java 类似,都是采用的类来创建线程。

threading模块的函数:

函数 说明
threading.active_count() 返回正运行的线程数量(包括主线程),与len(threading.enumerate())结果相同
threading.current_thread() 返回当前线程的变量
threading.main_thread() 返回主线程
threading.enumerate() 返回一个正在运行的线程的列表

先来对单线程和多线程做一个对比,代码示例如下:

不使用多线程的操作:

import time
def worker():
    print("worker")
    time.sleep(1)
    return

if __name__ == "__main__": 
    for i in range(5):
        worker()

运行结果:

"""
worker
worker
worker
worker
worker
"""

使用多线程并发的操作,花费时间要短很多:

import threading
import time

def worker():
    print("worker")
    time.sleep(1)
    return

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=worker)
        t.start()

运行结果:

"""
worker
worker
worker
worker
worker
"""

2.创建线程————start()方法

方法一:
在创建线程时必须先定义一个继承threading.Thread的类,然后重写子类中的run()方法,使用start()方法启动线程。

import threading
# 类必须继承threading.Thread
class threadTest(threading.Thread):
    # args为传入线程的参数,可根据自己的需求进行定义
    def __init__(self,args) -> None:
        # 初始化super()内的必须和类名一样
        super(threadTest,self).__init__()         
        self.args = args

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        print('test thread running...')
        print('args: ',self.args)
        return super().run()

if __name__ == "__main__":
    # 实例化
    test = threadTest('just test')
    # 启动线程。即运行run()方法
    test.start()

方法二:
使用函数的方式创建线程。

import threading
def threadTest(arg):
    print("test thread running...")
    print("args: ",arg)

if __name__ == "__main__":
    # target传入函数名,注意不要写参数
    # args target传入的函数需要传入的参数,注意传入参数以元组的形式
    thread = threading.Thread(target=threadTest,args=('just test',))
    # 启动线程
    thread.start()

两种方式的运行结果都一样:

"""
test thread running...
args:  just test
"""

需要注意的是:创建的线程为子线程,是主线程创建的子线程。

除 start() 方法外,threading.Thread类还提供了以下方法:

方法 说明
join(timeout) 表示主线程等待子线程timeout时长(s)后子线程若还未结束,就强制结束子线程,不设置则主线程会一直等待子线程结束后再结束
getName() 获取线程名
setName() 设置线程名
isAlive() 返回线程是否正在运行
ident() 获取线程标识符
setDaemon(bool) 设置主,子线程运行时的关系。bool为True时主线程结束,子线程立即结束;为false主,子线程运行毫不相关,独立运行

3.join()方法

join()常用于控制线程的运行方法,只能在线程启动后使用,设置主线程是否等待子线程的结束。

未使用join()方法:

import threading
import time

# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须和类名一样
        super(threadTest,self).__init__()

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        print('子线程' + self.getName() + '开始: ' + str(time.time()))
        # 休眠1s
        time.sleep(1)
        print('子线程' + self.getName() + '结束: ' + str(time.time()))

        return super().run()

if __name__ == "__main__":
    # 创建线程列表
    threads = [] 

    for i in range(1,5):
        test = threadTest()
        # 设置线程名称
        test.setName('Thread - %s' %(str(i)))
        threads.append(test)

    for thread in threads:
        # 启动线程
        thread.start()

print("主线程已结束: %s" %(str(time.time())))

运行结果:

"""
子线程Thread - 1开始: 1663308417.2721999
子线程Thread - 2开始: 1663308417.2724535
子线程Thread - 3开始: 1663308417.2726364
子线程Thread - 4开始: 1663308417.2727745
主线程已结束: 1663308417.272802
子线程Thread - 1结束: 1663308418.2736351
子线程Thread - 2结束: 1663308418.273736
子线程Thread - 3结束: 1663308418.2737775
子线程Thread - 4结束: 1663308418.2737951
"""

可以看到未使用join()方法时,多个子线程几乎是同时创建,同时结束的,并且主线程并没有等子线程结束就已经结束了。
下面是使用了join()方法。

import threading
import time

# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须和类名一样
        super(threadTest,self).__init__()

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        print('子线程' + self.getName() + '开始: ' + str(time.time()))
        # 休眠1s
        time.sleep(1)
        print('子线程' + self.getName() + '结束: ' + str(time.time()))

        return super().run()

if __name__ == "__main__":
    
    # 创建线程列表
    threads = []

    for i in range(1,5):
        test = threadTest()
        # 设置线程名称
        test.setName('Thread-%s' %(str(i)))
        threads.append(test)

    for thread in threads:
        # 启动线程
        thread.start()
        thread.join()

print("主线程已结束: %s" %(str(time.time())))

运行结果:

"""
子线程Thread-1开始: 1663310226.2752392
子线程Thread-1结束: 1663310227.349505
子线程Thread-2开始: 1663310227.3534868
子线程Thread-2结束: 1663310228.355835
子线程Thread-3开始: 1663310228.3568225
子线程Thread-3结束: 1663310229.3576124
子线程Thread-4开始: 1663310229.3605983
子线程Thread-4结束: 1663310230.3652728
主线程已结束: 1663310230.3662646
"""

可以看到使用了join()后线程是依次运行的,且主线程是等待子线程结束后再结束的。

4.setDaemon(bool)方法

setDaemon(bool)方法用于设置主线程的守护线程,在启动线程启动之前使用。

当bool为True时,该线程为守护线程,主线程结束,子线程立即结束;为false主,子线程运行毫不相关,独立运行,子线程继续运行到结束。

先来看一个简单的例子:

# threading.setDaemon()的使用,设置后台进程。
import time
import threading

def worker():
    time.sleep(3)
    print("worker")

t = threading.Thread(target=worker)
t.setDaemon(True)
t.start()
print("haha")

运行结果:

"""
haha
可以看出worker()方法中的打印操作并没有显示出来,说明已经成为后台进程。
"""

示例如下:

import threading
import time

# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须和类名一样
        super(threadTest,self).__init__()

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        print('子线程' + self.getName() + '开始: ' + str(time.time()))
        # 休眠3s
        time.sleep(3)
        print('子线程' + self.getName() + '结束: ' + str(time.time()))

        return super().run()

if __name__ == "__main__":
    print("主线程开始: %s" %(str(time.time())))
    thread = threadTest()
    # 设置线程名称
    thread.setName('Thread-1')
    # 参数设置为False
    thread.setDaemon(False)
    thread.start()
    time.sleep(1)
    print("主线程已结束: %s" %(str(time.time())))

运行结果:

"""
主线程开始: 1663313093.3144019
子线程Thread-1开始: 1663313093.4016862
主线程已结束: 1663313094.4025493
子线程Thread-1结束: 1663313096.4037333
"""

可以看到bool为false主线程结束,子线程继续运行到结束。

import threading
import time

# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须和类名一样
        super(threadTest,self).__init__()

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        print('子线程' + self.getName() + '开始: ' + str(time.time()))
        # 休眠3s
        time.sleep(3)
        print('子线程' + self.getName() + '结束: ' + str(time.time()))

        return super().run()

if __name__ == "__main__":
    print("主线程开始: %s" %(str(time.time())))
    thread = threadTest()
    # 设置线程名称
    thread.setName('Thread-1')
    # 参数设置为True
    thread.setDaemon(True)
    # 启动线程
    thread.start()
    time.sleep(1)
    print("主线程已结束: %s" %(str(time.time())))

运行结果:

"""
主线程开始: 1663314873.1436794
子线程Thread-1开始: 1663314873.2025218
主线程已结束: 1663314874.2020116
"""

可以看到bool为True时,主线程一结束,子线程就立即结束。

5.activeCount()方法

threading.activeCount()的使用,此方法返回当前进程中线程的个数。返回的个数中包含主线程。
示例如下:

import threading
import time

def worker():
    print("test")
    time.sleep(1)
    return

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=worker)
        t.start()

print("current has %d threads" %(threading.activeCount() - 1))

运行结果:

"""
test
test
test
test
current has 5 threads
test
"""

6.enumerate()方法

threading.enumerate()的使用,此方法返回当前运行中的Thread对象列表。

import time
import threading

def worker():
    print("test")
    time.sleep(2)

threads = []
if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=worker)
        threads.append(t)
        t.start()

for item in threading.enumerate():
    print(item)
print

运行结果:

"""
test
test
test
test
test
<_MainThread(MainThread, started 139837462284096)>
<Thread(Thread-1, started 139837333047040)>
<Thread(Thread-2, started 139837324654336)>
<Thread(Thread-3, started 139837316261632)>
<Thread(Thread-4, started 139837307868928)>
<Thread(Thread-5, started 139837299476224)>
"""

线程同步是多线程中很重要的概念,当多个线程需要共享数据时,如果不使用线程同步,就会存在数据不同步的情况。
要做到线程同步有两种方法,线程锁和条件变量Condition。

线程锁

1.Lock锁

threading模块中Lock锁和_thread模块中的锁是一样的。

代码示例:

import threading
import time

num = 0
# 申请线程锁
lock = threading.Lock()
# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须与类名一样
        super(threadTest,self).__init__()

    def run(self) -> None:
        # 声明全局变量num
        global num
        # 申请线程锁
        # lock.acquire()
        print('子线程' + self.getName() + '开始: ' + str(time.time()))

        while num < 5:
            # 休眠2s
            time.sleep(2)
            print(self.getName(), 'num: ', num)
            num += 1
        print('子线程' + self.getName() + '结束: ' + str(time.time()))
        
        # 释放线程锁
        # lock.release()

        return super().run()

if __name__ == "__main__":
    print('主线程开始: %s' %(str(time.time())))
    thread1 = threadTest()
    # 设置线程名称
    thread1.setName('Thread-1')
    thread2 = threadTest()
    # 设置线程名称
    thread2.setName('Thread-2')
    # 启动线程
    thread1.start()
    # 启动线程
    thread2.start()
    time.sleep(1)
    thread1.join()
    thread2.join()
    print('主线程已结束: %s' %(str(time.time())))

运行结果:

"""
主线程开始: 1663554562.694666
子线程Thread-1开始: 1663554562.766473
子线程Thread-2开始: 1663554562.7694726
Thread-1 num:  0
Thread-2 num:  1
Thread-1 num:  2
Thread-2 num:  3
Thread-1 num:  4
子线程Thread-1结束: 1663554568.7719545
Thread-2 num:  5
子线程Thread-2结束: 1663554568.7779303
主线程已结束: 1663554568.7779303
"""

可以看到在未使用锁线程时,线程1和线程2对num操作出现了混乱。
将上面代码 lock.acquire() lock.release()这两行代码的注释去掉,将线程锁添加。
运行结果如下:可以看到不会像上面出现混乱的情况。

import threading
import time

num = 0
# 申请线程锁
lock = threading.Lock()
# 类必须继承threading.Thread
class threadTest(threading.Thread):
    def __init__(self) -> None:
        # 初始化super()内的必须与类名一样
        super(threadTest,self).__init__()

    # 定义run()方法,主要写线程的执行内容
    def run(self) -> None:
        # 声明全局变量num
        global num

        # 申请线程锁
        lock.acquire()

        print('子线程' + self.getName() + '开始: ' + str(time.time()))

        while num < 5:
            time.sleep(2)
            print(self.getName(), 'num: ', num)
            num += 1
        
        print('子线程' + self.getName() + '结束: ' + str(time.time()))
        
        # 释放线程锁
        lock.release()

        return super().run()

if __name__ == "__main__":
    print('主线程开始: %s' %(str(time.time())))
    thread1 = threadTest()
    # 设置线程名称
    thread1.setName('Thread-1')
    thread2 = threadTest()
    # 设置线程名称
    thread2.setName('Thread-2')
    # 启动线程
    thread1.start()
    # 启动线程
    thread2.start()
    time.sleep(1)
    thread1.join()
    thread2.join()
    print('主线程已结束: %s' %(str(time.time())))

运行结果:

"""
主线程开始: 1663554622.1313546
子线程Thread-1开始: 1663554622.188828
Thread-1 num:  0
Thread-1 num:  1
Thread-1 num:  2
Thread-1 num:  3
Thread-1 num:  4
子线程Thread-1结束: 1663554632.1943903
子线程Thread-2开始: 1663554632.196366
子线程Thread-2结束: 1663554632.2123253
主线程已结束: 1663554632.214321
"""

2.RLock锁

RLock锁又称递归锁,其与Lock锁的差别在于,Lock锁只允许在同一线程中申请一次,否则线程会进入死锁,但是RLock允许在同一线程多次调用。
使用Lock锁产生死锁示例代码:

import threading
import time

print("主线程开始: %s" %(str(time.time())))
lock = threading.Lock()

# 申请线程锁
lock.acquire()
print(threading.enumerate())

# 再次申请线程锁,产生了死锁
lock.acquire()
print(threading.enumerate())

lock.release()
lock.release()

print("主线程结束: %s" %(str(time.time())))

运行结果:

此处为我在终端中主动杀死了进程,而非程序自己结束
"""
主线程开始: 1663555589.4122267
[<_MainThread(MainThread, started 5240)>, <WriterThread(pydevd.Writer, started daemon 31352)>, <ReaderThread(pydevd.Reader, started daemon 18392)>, 
<_TimeoutThread(Thread-4, started daemon 32708)>, <PyDBCommandThread(pydevd.CommandThread, started daemon 30700)>, <CheckAliveThread(pydevd.CheckAliveThread, started 9552)>]
"""

使用RLock锁不会产生死锁示例代码:


import threading
import time

print("主线程开始: %s" %(str(time.time())))

lock = threading.RLock()

# 申请线程锁
lock.acquire()
print(threading.enumerate())

# 再次申请线程锁,不会产生死锁
lock.acquire()
print(threading.enumerate())

lock.release()
lock.release()

print("主线程结束: %s" %(str(time.time())))

运行结果:

"""
主线程开始: 1663555970.6706088
[<_MainThread(MainThread, started 28800)>, <WriterThread(pydevd.Writer, started daemon 27228)>, <ReaderThread(pydevd.Reader, started daemon 32332)>, <_TimeoutThread(Thread-4, started daemon 26608)>, <PyDBCommandThread(pydevd.CommandThread, started daemon 30952)>, <CheckAliveThread(pydevd.CheckAliveThread, started 30732)>]
[<_MainThread(MainThread, started 28800)>, <WriterThread(pydevd.Writer, started daemon 27228)>, <ReaderThread(pydevd.Reader, started daemon 32332)>, <_TimeoutThread(Thread-4, started daemon 26608)>, <PyDBCommandThread(pydevd.CommandThread, started daemon 30952)>, <CheckAliveThread(pydevd.CheckAliveThread, started 30732)>]
主线程结束: 1663555970.7434158
"""

从上面可以看到Lock与RLock的区别
注意线程锁需要成对出现

条件变量 Condition

Condition是python3中一种更高级的锁,除和线程锁类似的 acquire() 和 release() 函数外,还提供以下函数。

函数 说明
wait() 使线程挂起
notify() 唤醒挂起的线程使其运行
notifyAll() 唤醒所有线程使其运行

注意:线程使用前需要获得锁,否则会抛出RuntimeError异常

可以理解为,Condition提供了一种多线程通信机制,若线程1需要数据,线程1就会阻塞等待,线程2制造出数据,等待线程2制造好数据并通知线程1后,线程1就可以去获取数据了

下面是一个使用条件变量 Condition 模拟成语接龙示例代码:

import threading
import time

# 类必须继承threading.Thread
class Man1(threading.Thread):    
    def __init__(self,lock) -> None:
        # 初始化super()内的必须和类名一样
        super(Man1,self).__init__()
        self.lock = lock

    def run(self):
        # 申请锁
        self.lock.acquire()
        print('子线程' + self.getName() + '为所欲为')

        # 挂起线程,等待回答
        self.lock.wait()
        print('子线程' + self.getName() + '逼上梁山')
        # 运行挂起的线程
        self.lock.notify()

        self.lock.wait()
        print('子线程' + self.getName() + '尽力而为')
        self.lock.notify()

        self.lock.release()

# 类必须继承threading.Thread
class Man2(threading.Thread):
    def __init__(self,lock) -> None:
        # 初始化super()内的必须和类名一样
        super(Man2,self).__init__()
        self.lock = lock

    def run(self):
        # 申请锁
        self.lock.acquire()
        # 唤醒对方线程
        self.lock.notify()
        print('子线程' + self.getName() + '为法自弊')
        # 挂起线程,等待回答
        self.lock.wait()

        self.lock.notify()
        print('子线程' + self.getName() + '山穷水尽')
        self.lock.wait()

        self.lock.notify()
        print('子线程' + self.getName() + '为所欲为')
        
        self.lock.release()

if __name__ == '__main__':
    lock = threading.Condition()
    man1 = Man1(lock)
    man2 = Man2(lock)

    # 设置线程名称
    man1.setName('Thread-1')
    # 设置线程名称
    man2.setName('Thread-2')
    print('成语接龙开始: ')
    man1.start()
    man2.start()

运行结果:

"""
成语接龙开始: 
子线程Thread-1为所欲为
子线程Thread-2为法自弊
子线程Thread-1逼上梁山
子线程Thread-2山穷水尽
子线程Thread-1尽力而为
子线程Thread-2为所欲为
"""

可以看到在条件变量的控制下,两个线程按照顺序执行,直到结束。

标签:__,Thread,Python,self,threading,线程,time,多线程
From: https://www.cnblogs.com/even160941/p/16708394.html

相关文章

  • python-程序控制-for
    1.for循环的一般形式fortmpVarinIterable:blocktmpVar是临时变量Iterable是可迭代对象第一行是循环条件,当对可迭代对象的迭代遍历结束时,for循......
  • Python: yield from
     importtimedefdubious():print(f"\033[32;40;7m{'dubiousstart'}\033[0m")whileTrue:send_value=yieldifsend_valueisNone:......
  • 力扣92(java&python)-反转链表Ⅱ(中等)
    题目:给你单链表的头指针head和两个整数 left和right,其中 left<=right。请你反转从位置left到位置right的链表节点,返回反转后的链表。示例1:输入:head=......
  • python主文件调用其他文件函数的方法
    关键:from文件名import函数名主文件(main.py)需要和包含子函数的文件(fun_cal_modulus8.py)放到同一路径下fun_cal_modulus8.pyfromnumpyimport*#8水平defc......
  • Python: 取消numpy科学计数法
    Numpy中默认是使用科学计数法来显示数据的,但是这种做法往往不利于我们观测数据,比如坐标数据等。那么如何取消numpy科学计数法呢,请往下看。np.set_printoptions()import......
  • 基于Python的求职招聘管理系统Django企业招聘管理系统(源码调试+讲解+文档)
    ......
  • python GUI编程
    GUI英文全称是GraphicalUserInterface,中文为图形用户接口。tkinter是pythonGUI编程的一个库。主要分为三步,首先建立一个窗口(设置参数),然后在窗口上放置组件(文本框、......
  • python 网络编程
    IP地址联网设备(电脑)每次介入网络,都会按照网络的规则,分配身份证号码,每个IP地址对应一个设备。每次进入网络IP地址未必相同。查看电脑IP地址windows:ipconfigIP地址就是I......
  • Python: __slots__
     __slots__定义为类属性,约束实例属性,类定义__slots__后,实例就没有__dict__ 子类和父类都定义__slots__后,子类可有全部__slots__属性  父类存在__slots......
  • python函数参数传递 可更改对象 不可更改对象
    1、函数分为内联函数和自定义函数2、参数传递:在python中,类型属于对象,变量是没有类型的例如:[1,2,3] 是List类型,"Runoob" 是String类型,而变量a是没有类型,她仅仅......