猪行天下之Python基础——9.2 Python多线程与多进程(中)(一)

简介: 内容简述:1、threading模块详解2、queue模块详解

1、threading模块详解


Python提供的与线程操作相关的模块,网上有很多资料还是用的thread模块,在3.x版本中已经使用threading来替代thread,如果你在python 2.x版本想使用threading的话,可以使用dummy_threading模块


① threading模块提供的可直接调用的函数


  • active_count():获取当前活跃(alive)线程的个数。


  • current_thread():获取当前的线程对象。


  • get_ident():返回当前线程的索引,一个非零的整数(3.3新增)。


  • enumerate():获取当前所有活跃线程的列表。


  • main_thread():返回主线程对象(3.4新增)。


  • settrace(func):设置一个回调函数,在run()执行之前被调用。


  • setprofile(func):设置一个回调函数,在run()执行完毕之后调用。


  • stack_size():返回创建新线程时使用的线程堆栈大小。


  • threading.TIMEOUT_MAX:堵塞线程时间最大值,超过这个值会栈溢出。


② 线程局部变量(Thread-Local Data)


问题引入:


在一个进程内所有的线程共享进程的全局变量,线程间共享数据很方便但是每个线程都可以随意修改全局变量,可能会引起线程安全问题。


解决方法:


对于这种线程私有数据,最简单的方法就是对变量加锁或使用局部变量,只有线程自身可以访问,其他线程无法访问。除此之外还可以使用threading模块为我们提供的ThreadLocal变量,它本身是一个全局变量,但是线程们却可以使用它来保存私有数据。


用法简介:


定义一个全局变量:data = thread.local(),然后就可以往里面存数据啦,比如data.num = xxx,但是有一点要注意:如果data里没有设置对应的属性,直接取会报AttributeError异常,使用时可以捕获这个异常或先调用hasattr(对象,属性)判断对象中是否有该属性!使用代码示例如下:


import threading
import random
data = threading.local()
def show(d):
    try:
        num = d.num
    except AttributeError:
        print("线程 %s 还未设置该属性!" % threading.current_thread().getName())
    else:
        print("线程 %s 中该属性的值为 = %s" % (threading.current_thread().getName(), num))
def thread_call(d):
    show(d)
    d.num = random.randint(1, 100)
    show(d)
if __name__ == '__main__':
    show(data)
    data.num = 666
    show(data)
    for i in range(2):
        t = threading.Thread(target=thread_call, args=(data,), name="Thread " + str(i))
        t.start()


运行结果如下


线程 MainThread 还未设置该属性!
线程 MainThread 中该属性的值为 = 666
线程 Thread 0 还未设置该属性!
线程 Thread 0 中该属性的值为 = 80
线程 Thread 1 还未设置该属性!
线程 Thread 1 中该属性的值为 = 17


不同线程访问这个ThreadLocal变量,返回的都是不一样的值,原理:


threading.local()实例化一个全局对象,这个全局对象里有一个大字典,键值为两个弱引用对象{线程对象,字典对象},然后可以通过current_thread()获得当前的线程对象,然后根据这个对象可以拿到对应的字典对象,然后进行参数的读或者写。


③ 线程对象(threading.Thread)


创建新线程的两种方式:


  • 1.直接创建threading.Thread对象并把调用对象作为参数传入


  • 2.继承threading.Thread类重写run()方法


使用代码示例(验证单线程快还是多线程快):


import threading
import time
def catch_fish():
    pass
def one_thread():
    start_time = time.time()
    for i in range(1, 1001):
        catch_fish()
    end_time = time.time()
    print("单线程测试 耗时 === %s" % str(end_time - start_time))
def muti_thread():
    start_time = time.time()
    for i in range(1, 1001):
        threading.Thread(target=catch_fish()).start()
    end_time = time.time()
    print("多线程测试 耗时 === %s" % str(end_time - start_time))
if __name__ == '__main__':
    # 单线程
    threading.Thread(one_thread()).start()
    # 多线程
    muti_thread()


运行结果如下:


单线程测试 耗时 === 0.00011301040649414062
多线程测试 耗时 === 0.07665514945983887


从输出结果可以看到,多线程反而比单线程要慢,原因是前面介绍过的Python中的全局解释器锁(GIL), 使得任何时候仅有一个线程在执行。


Thread类构造函数


def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):


构造函数参数依次是:


  • group:线程组


  • target:要执行的函数


  • name:线程名字


  • args/kwargs:要传入的函数的参数


  • daemon:是否为守护线程


相关属性与函数:


  • start():启动线程,只能调用一次


  • run():线程执行的操作,可继承Thread重写,参数可从args和kwargs获取;


  • join([timeout]):堵塞调用线程,直到被调用线程运行结束或超时;如果
    没设置超时时间会一直堵塞到被调用线程结束。


  • name/getName():获得线程名;


  • setName():设置线程名;


  • ident:线程是已经启动,未启动会返回一个非零整数;


  • is_alive():判断是否在运行,启动后,终止前;


  • daemon/isDaemon():线程是否为守护线程;


  • setDaemon():设置线程为守护线程;


④ Lock(指令锁)与RLock(可重入锁)


在概念那里就讲了,多个进程并发的访问临界资源可能会引起线程同步安全问题,写个简单的例子,然后再引入同步锁。代码示例如下:


import threading
file_name = "test.txt"
# 定义一个写入文件的方法
def write_to_file(msg):
    try:
        with open(file_name, "a+", encoding="utf-8") as f:
            f.write(msg + "\n")
    except OSError as reason:
        print(str(reason))
class MyThread(threading.Thread):
    def __init__(self, msg):
        super().__init__()
        self.msg = msg
    def run(self):
        write_to_file(self.name + "~" + self.msg)
if __name__ == '__main__':
    for i in range(1, 21):
        t = MyThread(str(i)).start()


运行结果如下


# test.txt文件内容
Thread-1~1
Thread-5~5
Thread-3~3
Thread-2~2
Thread-4~4
Thread-6~6
Thread-7~7
Thread-8~8
Thread-10~10
Thread-9~9
Thread-11~11
Thread-13~13
Thread-12~12
Thread-14~14
Thread-15~15
Thread-16~16
Thread-17~17
Thread-19~19
Thread-20~20
Thread-18~18

发现结果并没有按照我们预想的1-20那样顺序打印,而是乱的,threading模块中提供了两个类来确保多线程共享资源的访问:「Lock」 和 「RLock」。


相关文章
|
13天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
7天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
26 0
|
22天前
|
数据采集 Java API
python并发编程: Python使用线程池在Web服务中实现加速
python并发编程: Python使用线程池在Web服务中实现加速
18 3
python并发编程: Python使用线程池在Web服务中实现加速
|
30天前
|
消息中间件 Linux 调度
【Linux 进程/线程状态 】深入理解Linux C++中的进程/线程状态:阻塞,休眠,僵死
【Linux 进程/线程状态 】深入理解Linux C++中的进程/线程状态:阻塞,休眠,僵死
67 0
|
1天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
1天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
1天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
1天前
|
存储 Linux Shell
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
|
2天前
|
存储 安全 Linux
Linux的学习之路:9、冯诺依曼与进程(1)
Linux的学习之路:9、冯诺依曼与进程(1)
17 0
|
8天前
|
监控 Linux
linux监控指定进程
请注意,以上步骤提供了一种基本的方式来监控指定进程。根据你的需求,你可以选择使用不同的工具和参数来获取更详细的进程信息。
14 0