深入理解Python多任务编程----多线程

简介: 深入理解Python多任务编程----多线程

计算机的设计就是为了帮助人类或者模仿人类的某些行为。

生活中的多任务:人可以一边唱歌????一边跳舞????、人开车的时候是通过手、脚和眼睛共同配合来驾驶一辆车????。

多任务编程就是这样一个鲜明的例子,计算机也可以实现多任务编程:比如一边听歌一边玩游戏、打开浏览器上网同时能登录微信、QQ等聊天工具。

那么Python的多任务有哪些方式呢?

 Python多任务编程的三种方式

  • 多线程
  • 多进程
  • 协程

今天我们先来聊一聊Python的多线程编程。

 线程

有两种不同类型的线程:

  • 内核线程
  • 用户空间线程或用户线程

内核线程是操作系统的一部分,而用户空间线程未在内核中实现,关于线程和进程的更多概念请点此处

Python中的线程

Python中有两个关于线程的模块:

  • thread
  • threading

Ps:一直以来,thread模块一直都不被推荐使用,鼓励推荐使用threading模块,所以在Python3中的向后兼容,thread模块被重命名为_thread

>>> import thread
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    import thread
ModuleNotFoundError: No module named 'thread'
>>> import _thread
>>> 

threading 模块自 Python 1.5.1(1998 年)就已存在,不过有些人仍然继续使用旧的 thread 模块。Python 3 把 thread 模块重命名为 _thread,以此强调这是低层实现,不应该在应用代码中使用。

thread模块

可以使用Thread模块在单独的线程中执行功能。为此,我们可以使用函数thread.start_new_thread

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

此方法可以快速有效地在Linux和Windows中创建新线程。这个方法先接收一个函数对象(或其他可调用对象)和一个参数元组,然后开启新线程来执行所传入的函数对象及其传入的参数。

import _thread
def child(tid):
    print("Hello from thread", tid)
def parent():
    i = 0
    while True:
        i += 1
        _thread.start_new_thread(child, (i,))  # 创建线程的调用
        if input() == 'q':
            break
parent()

我们运行上段程序,然后只要不在控制台输入q,就能看到不断有新的线程创建和退出。当主线程退出时,整个线程就随之退出了。

Hello from thread 1
Hello from thread 2
Hello from thread 3
Hello from thread 4
Hello from thread 5
q

多线程唱歌跳舞

假如我们让电脑????模拟唱跳,就需要启动两个线程,同时利用time.sleep避免主线程过早退出,但是线程输出可能随机。

from _thread import start_new_thread
import time
def sing():
    for i in range(3):
        print("I'm singing 难忘今宵")
        time.sleep(2)
def dance():
    for i in range(3):
        print("I'm dancing")
        time.sleep(2)
def main():
    start_new_thread(sing, ())
    start_new_thread(dance, ())
    time.sleep(8)
    print('Main thread exiting...')
if __name__ == '__main__':
    main()

如上代码,我们需要唱3遍“难忘今宵”,同时跳三遍伴舞。time.sleep(8)避免主线程过早退出导致新建的singdance线程提前退出,所以输出结果可能(每次执行的输出可能不一样):

I'm singing 难忘今宵
I'm dancing
I'm dancing
I'm singing 难忘今宵
I'm dancing
I'm singing 难忘今宵
Main thread exiting...

输出结果的不规律是因为所有的线程的函数调用都在同一进程中运行,它们共享一个标准输出流,2个并行运行的线程输出都混杂在一起了。

更为重要的是,多个线程访问共享资源时,必须同步化访问以避免时间上的重叠。

我们为了防止主线程退出,整个程序终止,达不到自己想到的效果,利用了sleep()来作为同步机制,由于这个延时,整个程序的运行时间并没有比单线程的版本更快,而且多个线程一起共享某个变量/对象,那么就有可能会丢失其中一个。

我们看一下如下代码:

from _thread import start_new_thread
import time
num = 0
def plus_one():
    global num
    for i in range(1000):
        num += 1
def minus_one():
    global num
    for i in range(1000):
        num -= 1
def main():
    start_new_thread(plus_one, ())
    start_new_thread(minus_one, ())
    time.sleep(3)
    print(num)
if __name__ == '__main__':
    main()

我们共享一个全局变量num,启动两个线程:一个加一1000次,一个减一1000次,最后输出num的值,好像为0,但是果真如此吗?我们是一下循环100000次看看,

from _thread import start_new_thread
import time
num = 0
def plus_one():
    global num
    for i in range(100000):
        num += 1
def minus_one():
    global num
    for i in range(100000):
        num -= 1
def main():
    start_new_thread(plus_one, ())
    start_new_thread(minus_one, ())
    time.sleep(3)
    print(num)
if __name__ == '__main__':
    main()

输出num结果可能为整数,也可能为负数,也可能为0,这是因为线程执行顺序其实是随机的。

锁的概念

正因为存在上述的问题,所以引出锁的概念:想要修改一个共享对象,线程需要获得一把锁,然后进行修改,之后释放这把锁,然后才能被其他线程获取。通过allocate_lock()创建一个锁的对象,例如:

from _thread import start_new_thread, allocate_lock
import time
num = 0
mutex = allocate_lock()  # 增加一把锁
def plus_one():
    global num
    mutex.acquire()  # 获得锁
    for i in range(1000000):
        num += 1
    mutex.release()  # 释放锁
def minus_one():
    global num
    mutex.acquire()  # 获得锁
    for i in range(1000000):
        num -= 1
    mutex.release()  # 释放锁
def main():
    start_new_thread(plus_one, ())
    start_new_thread(minus_one, ())
    time.sleep(3)
    print(num)
if __name__ == '__main__':
    main()

这样执行后结果就会一直是0。

threading模块

threading是基于对象和类的较高层面上的接口,

threading.Thread((target=function_name, args=(function_parameter1, function_parameterN))

我们也首先实现一个上述加一减一的操作。

import threading
num = 0
def plus_one():
    global num
    for i in range(1000000):
        num += 1
def minus_one():
    global num
    for i in range(1000000):
        num -= 1
def main():
    t1 = threading.Thread(target=plus_one)
    t2 = threading.Thread(target=minus_one)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(num)
if __name__ == '__main__':
    main()

上锁

import threading
num = 0
mutex = threading.Lock()
def plus_one():
    global num
    mutex.acquire()
    for i in range(1000000):
        num += 1
    mutex.release()
def minus_one():
    global num
    mutex.acquire()
    for i in range(1000000):
        num -= 1
    mutex.release()
def main():
    t1 = threading.Thread(target=plus_one)
    t2 = threading.Thread(target=minus_one)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(num)
if __name__ == '__main__':
    main()

到此,我们简单的介绍了Python中两个关于线程的模块,然后通过共享变量引出锁的概念,不过到此并没有结束。比如:自定义线程、守护线程、死锁…

相关文章
|
1天前
|
运维 Prometheus 监控
自动化运维的魔法:使用Python脚本简化日常任务
【8月更文挑战第50天】在数字化时代的浪潮中,自动化运维成为提升效率、减少人为错误的利器。本文将通过一个实际案例,展示如何利用Python脚本实现自动化部署和监控,从而让运维工作变得更加轻松和高效。我们将一起探索代码的力量,解锁自动化运维的神秘面纱,让你的工作环境焕然一新。
116 81
|
2天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
15 4
|
10天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
1天前
|
安全 Java 调度
Java 并发编程中的线程安全和性能优化
本文将深入探讨Java并发编程中的关键概念,包括线程安全、同步机制以及性能优化。我们将从基础入手,逐步解析高级技术,并通过实例展示如何在实际开发中应用这些知识。阅读完本文后,读者将对如何在多线程环境中编写高效且安全的Java代码有一个全面的了解。
|
6天前
|
运维 监控 Linux
自动化运维的魔法:如何用Python脚本简化日常任务
【9月更文挑战第13天】在数字化时代的浪潮中,自动化运维如同一股清流,为IT团队带来了效率和灵活性的双重提升。本文将深入探讨如何通过Python脚本实现日常运维任务的自动化,从而释放双手,让重复性工作变得轻松愉快。从环境搭建到实际案例分析,我们将一步步揭开自动化运维的神秘面纱,让你的运维之路更加顺畅。
|
11天前
|
API Python
探索Python中的多线程编程
探索Python中的多线程编程
32 5
|
11天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
13天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
17 1
|
16天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
17天前
|
运维 Linux 测试技术
自动化运维:使用Python脚本简化日常任务
【8月更文挑战第34天】在快节奏的IT环境中,自动化运维成为提升效率、降低错误率的关键。本文以Python脚本为例,展示如何通过编写简单的脚本来自动化日常运维任务,如批量更改文件权限、自动备份数据等。文章不仅提供代码示例,还探讨了自动化运维带来的益处和实施时应注意的问题。