Python系列(10)- Python 多线程


多线程(Multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的系统包括对称多处理机、多核心处理器、芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作 “线程”(Thread),利用它编程的概念就叫作 “多线程处理”。

多线程是并行化的一种形式,或者是拆分工作以便同时进行处理。线程化的程序将工作拆分到多个软件线程,而不是将大量工作交给单个内核。这些线程由不同的 CPU 内核并行处理,以节省时间。

多线程运行有如下优点:

    (1) 使用线程可以把占据长时间的程序中的任务放到后台去处理;
    (2) 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度;
    (3) 程序的运行速度可能加快;
    (4) 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。

线程可以分为:

    (1) 内核线程:由操作系统内核创建和撤销。
    (2) 用户线程:不需要内核支持而在用户程序中实现的线程。

并行和并发的区别:

  并行(Parallelism):是同一时刻,每个线程都在执行。
  并发(Concurrency):是同一时刻,只有一个线程执行,然后交替执行。Python 多线程是并发的。

Python 中与多线程相关的 2 个模块:

    (1) _thread
    (2) threading (推荐使用)

    注:thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用 "thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。

 

1. _thread 模块

    _thread 模块提供低级别的接口,用于支持轻量级的多线程开发,也提供了简单的锁机制(也称为 互斥锁 或 二进制信号)。

    调用 _thread 模块中的 start_new_thread( )函数来产生新线程。语法格式如下:

        _thread.start_new_thread(function, args[, kwargs])

    参数说明:

        function - 线程函数。
        args - 传递给线程函数的参数,他必须是个tuple类型。
        kwargs - 可选参数。

    示例:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import _thread
        import time

        def print_time( name, delay):
            count = 0
            while count < 3:
                time.sleep(delay)
                count += 1
                print ("%s: %s" % ( name, time.ctime(time.time()) ))
            print("%s 结束" % name)

        _thread.start_new_thread(print_time, ("Thread-1", 2))
        _thread.start_new_thread(print_time, ("Thread-2", 4))

        print("同时按下 Ctrl + C 键退出")
        while True:
            pass

    输出结果如下:

        同时按下 Ctrl + C 键退出
        Thread-1: Sat Aug 24 13:02:54 2024
        Thread-2: Sat Aug 24 13:02:56 2024
        Thread-1: Sat Aug 24 13:02:56 2024
        Thread-1: Sat Aug 24 13:02:58 2024
        Thread-1 结束
        Thread-2: Sat Aug 24 13:03:00 2024
        Thread-2: Sat Aug 24 13:03:04 2024
        Thread-2 结束
        Traceback (most recent call last):
        File "d:/pythonDemo/thread.py", line 23, in <module>
            pass
        KeyboardInterrupt

        注:两个线程结束后,主程序仍然处于 while True 循环阻塞,需要在控制台按下 Ctrl + C 键才能退出。
            
    显然,while True 循环阻塞不是很好的线程同步方式,我们可以使用 _thread.allocate_lock() 来实现线程同步,修改代码如下:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import _thread
        import time

        def print_time( name, delay, lock):
            count = 0
            while count < 3:
                time.sleep(delay)
                count += 1
                print ("%s: %s" % ( name, time.ctime(time.time()) ))
            print("%s 结束" % name)
            lock.release()

        lock1 = _thread.allocate_lock()
        lock1.acquire()
        _thread.start_new_thread(print_time, ("Thread-1", 2, lock1))

        lock2 = _thread.allocate_lock()
        lock2.acquire()
        _thread.start_new_thread(print_time, ("Thread-2", 4, lock2))

        while lock1.locked() or lock2.locked():
            pass


    输出结果如下:

        Thread-1: Sat Aug 24 17:24:14 2024
        Thread-2: Sat Aug 24 17:24:16 2024
        Thread-1: Sat Aug 24 17:24:16 2024
        Thread-1: Sat Aug 24 17:24:18 2024
        Thread-1 结束
        Thread-2: Sat Aug 24 17:24:20 2024
        Thread-2: Sat Aug 24 17:24:24 2024
        Thread-2 结束

      注:两个线程结束后,主程序自动退出。


2. threading 模块

    threading 模块除了包含 _thread 模块中的所有方法外,还提供如下方法:

        (1) threading.Thread(target, args=(), kwargs={}, daemon=None): 创建 Thread 类的实例;
        (2) threading.current_thread(): 返回当前的线程变量;
        (3) threading.enumerate(): 返回一个包含正在运行的线程的列表。正在运行指线程启动后、结束前,不包括启动前和终止后的线程;
        (4) threading.active_count(): 返回正在运行的线程数量,与 len(threading.enumerate()) 有相同的结果;

    threading.Thread() 返回的 Thread 实例包含如下方法与属性:

        (1) start():开始线程活动(只能执行一次);
        (2) run():线程的方法,在线程被 cpu 调度后,就会自动执行这个方法,可以重写;
        (3) join():等待,直到线程结束;
        (4) setName():给线程设置名字;
        (5) getName():获取线程名字;
        (6) is_alive():返回线程是否存活( True 或者 False);
        (7) daemon: 守护线程,默认是 False;

    1) 使用 threading.Thread() 创建线程

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            import threading
            import time

            def print_time( name, delay):
                count = 0
                while count < 3:
                    time.sleep(delay)
                    count += 1
                    print ("%s: %s" % ( name, time.ctime(time.time()) ))
                print("%s finish" % name)

            thread1 = threading.Thread(target=print_time, args=("Thread-1", 2))
            thread2 = threading.Thread(target=print_time, args=("Thread-2", 4))

            thread1.start()
            thread2.start()

            print('主程序结束')

        输出结果如下:

            主程序结束
            Thread-1: Sat Aug 24 14:05:32 2024
            Thread-2: Sat Aug 24 14:05:34 2024
            Thread-1: Sat Aug 24 14:05:34 2024
            Thread-1: Sat Aug 24 14:05:36 2024
            Thread-1 结束
            Thread-2: Sat Aug 24 14:05:38 2024
            Thread-2: Sat Aug 24 14:05:42 2024
            Thread-2 结束


    2) 继承 threading.Thread 创建子类线程

        示例:

            #!/usr/bin/python3
            # -*- coding: UTF-8 -*-

            import threading
            import time

            class TestThread(threading.Thread):
                def __init__(self, name, delay):
                    super().__init__()
                    self.name = name
                    self.delay = delay

                def run(self):
                    print_time(self.name, self.delay)
                    print("%s 结束" % self.name)

            def print_time( name, delay):
                count = 0
                while count < 3:
                    time.sleep(delay)
                    count += 1
                    print ("%s: %s" % ( name, time.ctime(time.time()) ))
                
            start = time.time()

            thread1 = TestThread("Thread-1", 2)
            thread2 = TestThread("Thread-2", 4)

            thread1.start()
            thread2.start()

            # 等待线程都结束
            thread1.join()
            thread2.join()

            end = time.time()

            print('主程序结束', int(end-start), '')

        输出结果如下:

            Thread-1: Sat Aug 24 17:40:40 2024
            Thread-2: Sat Aug 24 17:40:42 2024
            Thread-1: Sat Aug 24 17:40:42 2024
            Thread-1: Sat Aug 24 17:40:44 2024
            Thread-1 结束
            Thread-2: Sat Aug 24 17:40:46 2024
            Thread-2: Sat Aug 24 17:40:50 2024
            Thread-2 结束
            主程序结束 12 秒

        注:在主程序里调用线程的 join() 方法,实现多个线程的同步。

 

3. 线程锁

    有些场景我们不希望多个线程同时读写同一个数据,为了保证数据的正确性,需要管理线程对数据读写。

    可以使用 threading 模块的 Lock 或 Rlock 线程锁,两者都有 acquire 方法和 release 方法,可以将数据的读写操作放在 acquire 和 release 方法之间。

    示例:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import threading
        import time

        class TestThread(threading.Thread):
            def __init__(self, name, delay):
                super().__init__()
                self.name = name
                self.delay = delay

            def run(self):
                threadLock.acquire()
                print_time(self.name, self.delay)
                print("%s 结束" % self.name)
                threadLock.release()

        def print_time( name, delay):
            count = 0
            while count < 3:
                time.sleep(delay)
                count += 1
                print ("%s: %s" % ( name, time.ctime(time.time()) ))

        threadLock = threading.Lock()

        start = time.time()

        thread1 = TestThread("Thread-1", 2)
        thread2 = TestThread("Thread-2", 4)

        thread1.start()
        thread2.start()

        thread1.join()
        thread2.join()

        end = time.time()

        print('主程序结束', int(end-start), '')

    输出结果如下:

        Thread-1: Sat Aug 24 18:42:01 2024
        Thread-1: Sat Aug 24 18:42:03 2024
        Thread-1: Sat Aug 24 18:42:05 2024
        Thread-1 结束
        Thread-2: Sat Aug 24 18:42:09 2024
        Thread-2: Sat Aug 24 18:42:13 2024
        Thread-2: Sat Aug 24 18:42:17 2024
        Thread-2 结束
        主程序结束 18 秒            

    注:使用了线程锁之后,Thread-2 等 Thread-1 结束后才开始操作数据,程序总用时比没使用线程锁时增加了 6 秒。


4. 守护线程

    使用 threading 模块创建的线程,有两种类型:守护线程(Daemon thread)和非守护线程(Non-daemon thread)。默认情况下,当主线程结束时,所有非守护线程会继续运行。而守护线程会在主线程结束时自动退出。

    示例:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import threading
        import time

        def print_time( name, delay):
            while True:
                time.sleep(delay)
                print ("%s: %s" % ( name, time.ctime(time.time()) ))

        thread1 = threading.Thread(target=print_time, args=("Thread-1", 2))
        thread2 = threading.Thread(target=print_time, args=("Thread-2", 4))

        print('thread1.daemon:', thread1.daemon)
        print('thread2.daemon:', thread2.daemon)

        thread1.start()
        thread2.start()

        print('主程序结束')


    输出结果如下:

        thread1.daemon: False
        thread2.daemon: False
        主程序结束
        Thread-1: Sat Aug 24 20:58:18 2024
        Thread-2: Sat Aug 24 20:58:20 2024
        Thread-1: Sat Aug 24 20:58:20 2024
        Thread-1: Sat Aug 24 20:58:22 2024
        Thread-2: Sat Aug 24 20:58:24 2024
        Thread-1: Sat Aug 24 20:58:24 2024
        Thread-1: Sat Aug 24 20:58:26 2024
        Thread-2: Sat Aug 24 20:58:28 2024
        ...

        注:当主线程结束时,非守护线程还会继续运行。
    
    我们把 thread1、thread2 都改成守护线程,修改代码如下:

         #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import threading
        import time

        def print_time( name, delay):
            while True:
                time.sleep(delay)
                print ("%s: %s" % ( name, time.ctime(time.time()) ))

        thread1 = threading.Thread(target=print_time, args=("Thread-1", 2))
        thread2 = threading.Thread(target=print_time, args=("Thread-2", 4))

        thread1.daemon = True
        thread2.daemon = True

        print('thread1.daemon:', thread1.daemon)
        print('thread2.daemon:', thread2.daemon)

        thread1.start()
        thread2.start()

        print('主程序结束')

    输出结果如下:       

        thread1.daemon: True
        thread2.daemon: True
        主程序结束

        注:当主线程结束时,守护线程自动退出了。

 

5. 线程优先级队列(Queue)

    Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。

    这些队列都实现了锁原语 (Lock primitive),能够在多线程中直接使用,可以使用队列来实现线程间的同步。


    Queue 模块中的常用方法:

        (1) qsize():返回队列的大小;
        (2) empty():如果队列为空,返回 True,反之 False;
        (3) full():如果队列满了,返回 True,反之 False;
        (4) get([block[, timeout]]):获取队列,timeout 等待时间;
        (5) get_nowait():相当于 get(False);
        (6) put(item):写入队列;
        (7) put_nowait(item):相当于 put(item, False);
        (8) task_done():在完成一项工作之后,Queue.task_done() 函数向任务已经完成的队列发送一个信号;
        (9) join():实际上意味着等到队列为空,再执行别的操作;

    示例:

        #!/usr/bin/python3
        # -*- coding: UTF-8 -*-

        import queue
        import threading
        import time

        class TestThread(threading.Thread):
            def __init__(self, name, delay, q):
                super().__init__()
                self.name = name
                self.delay = delay
                self.q = q

            def run(self):
                print_time(self.name, self.delay, self.q)
                print("%s 结束" % self.name)

        def print_time( name, delay, q):
            while q.qsize() > 0:
                i = q.get()
                time.sleep(delay)
                print ("%s: %s" % ( name, time.ctime(time.time()) ))
                q.task_done()   # 任务处理完成,通知队列

        start = time.time()

        # 创建 Queue 任务队列
        q = queue.Queue()
        for i in range(6):
            q.put(i)

        print('Queue 队列: ', q.qsize(), '任务')

        thread1 = TestThread("Thread-1", 2, q)
        thread2 = TestThread("Thread-2", 4, q)

        thread1.start()
        thread2.start()

        q.join()

        end = time.time()

        print('主程序结束', int(end-start), '')


    输出结果如下:

        Queue 队列:  6 任务
        Thread-1: Sat Aug 24 21:21:49 2024
        Thread-1: Sat Aug 24 21:21:51 2024
        Thread-2: Sat Aug 24 21:21:51 2024
        Thread-1: Sat Aug 24 21:21:53 2024
        Thread-1: Sat Aug 24 21:21:55 2024
        Thread-2: Sat Aug 24 21:21:55 2024
        Thread-1 结束
        Thread-2 结束
        主程序结束 8 秒

 

posted @ 2024-08-28 11:05  垄山小站  阅读(102)  评论(0编辑  收藏  举报