Python 编程 | 连载 26 - Python 多线程

简介: Python 编程 | 连载 26 - Python 多线程

一、多线程

线程是系统的最小调度单元,线程相比进程来说,对于资源的消耗低。线程可以通过threading模块下Thread函数来创建,线程对象的相关方法有:

  • Thread:创建线程,入参需要传入函数名以及函数的参数,返回一个线程对象
  • start:启动线程
  • join:阻塞直到线程执行结束
  • getName:获取线程名
  • setName:设置线程名
  • is_alive:判断线程是否存活
  • setDaemon:守护线程

通过random.choice函数选中一个列中的元素,从列表中移除该元素并加入另外一个列表,直至列表为空。

import random, time
heros = ['stark', 'clint', 'thor', 'hulk', 'widow', 'captain', 'park', 'loki', 'strange', 'wanda']
_heros = []
def create():
    if len(heros) == 0:
        return
    hero = random.choice(heros)
    heros.remove(hero)
    print('移除的元素为:{}'.format(hero))
    _heros.append(hero)
    time.sleep(1)
if __name__ == '__main__':
    start = time.time()
    for i in range(len(heros)):
        create()
    print('heros: {}'.format(heros))
    print('_heros: {}'.format(_heros))
    end = time.time()
    print('耗时: {}'.format(end - start))
复制代码

28e1f3b462a2442fac68393b5c58dd6a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

使用多线程方式处理,首先导入threading模块,修改main函数中的代码。

if __name__ == '__main__':
    start = time.time()
    threads = []
    for i in range(len(heros)):
        # 创建线程执行任务
        thread = threading.Thread(target=create)
        threads.append(thread)
        thread.start()
        print('线程名为:{}'.format(thread.getName()))
    for thread in threads:
        thread.join()
        # create()
    print('heros: {}'.format(heros))
    print('_heros: {}'.format(_heros))
    end = time.time()
    print('耗时: {}'.format(end - start))
复制代码

image.png

循环创建了10个线程,每个线程都去执行任务,整个耗时非常短。

通过线程执行任务存在的问题:

  • 函数无法获取返回值
  • 多个线程同时修改文件可能造成数据混乱
  • 线程太多可能会造成资源不足

二、线程之间的通信

线程之间通信同样需要使用到队列。

import threading, time, queue, random
def send():
    while True:
        mes = random.randint(1, 10)
        print('send message {}'.format(mes))
        queue.put(mes)
        time.sleep(1)
def receive():
    while True:
        x = queue.get()
        print('receive {}'.format(x))
        time.sleep(1)
if __name__ == '__main__':
    queue = queue.Queue()
    t1 = threading.Thread(target=send)
    t2 = threading.Thread(target=receive)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
复制代码

image.png

线程池的创建

线程池可以避免线程创建和销毁带来的消耗,线程池需要通过futures.ThreadPoolExecutir方法来创建,线程池相关的方法有

  • futures.ThreadPoolExecutor:创建线程池
  • submit:往线程池中加入任务
  • done:判断线程池中的某个线程是否完成任务
  • result:获取线程执行的结果

首先导入concurrent.futures.thread包

import time, threading
from concurrent.futures.thread import ThreadPoolExecutor
def hallo(info):
    print(info)
    time.sleep(1)
if __name__ == '__main__':
    # 创建线程池
    pool = ThreadPoolExecutor(2)
    for i in range(20):
        pool.submit(hallo, ('Mark {}'.format(i)))
复制代码

image.png

控制台输出结果时几乎是两条信息同时打印,这是因为此时有两个线程正在执行任务。

# 创建一个线程锁
lock = threading.Lock()
def hallo(info):
    # 上锁
    lock.acquire()
    print(info)
    time.sleep(1)
    # 开锁
    lock.release()
复制代码

image.png

上了线程锁,就只能一个一个的执行了。

修改hallo()函数,返回info参数,并将上锁和解锁代码注释

# 其余代码不变
def hallo(info):
    # 上锁
    # lock.acquire()
    # print(info)
    time.sleep(1)
    # 开锁
    # lock.release()
    # print('PID:{}'.format(os.getpid()))
    return info
if __name__ == '__main__':
    # hallo()函数返回结果的列表
    results = []
    # 创建线程池
    pool = ThreadPoolExecutor(2)
    for i in range(20):
        res = pool.submit(hallo, ('Mark {}'.format(i)))
        results.append(res)
    print(res)
    print(dir(res))
    print('是否执行结束:{}'.format(res.done()))
    # 遍历结果列表
    for res in results:
        print('遍历结果列表:{}'.format(res.result()))
    print('是否执行结束:{}'.format(res.done()))
复制代码

image.png

线程池通过submit提交一个任务执行,返回一个Future对象,可以从该对象中通过调用result()函数获取任务执行的返回值。

GIL全局锁

Python 解释器在执行的时候自动加的一把锁,造成Python中的多线程无法在多个core执行,只能在一个core上执行,这把锁就是GIL锁。GIL是全局解释器锁,并不是Python的特性,它是在Cpython解释器里引入的一个概念,而在其他语言编写的解释器里没有GIL。

GIL锁的作用:

  • 单一CPU工作
  • 确保线程安全

pypy解释器是没有GIL全局锁的,但是不推荐使用pypy解释器,推荐多进程+多线程的方式,通过多个进程在多个CPU上执行,每个进程在执行多个线程。

在CPython解释其中,当Python代码有一个线程开始访问解释器的时候,GIL就会给这个线程上锁,此时此刻线程只能等着,无法对解释器的资源进行访问,需要等待线程分配时间,这个线程把锁释放,另外的线程才开始运行。

三、异步

异步是相对于同步而言的,同步既指程序按照顺序一步一步往下执行,异步就是无序,无序等待上一步完成之后才可以执行下一步。

异步编程是一种并发编程的模式,其关注点是通过调度不同任务之间的执行和等待时间,通过减少处理器的闲置时间来达到减少整个程序的执行时间;异步编程跟同步编程模型最大的不同就是其任务的切换,当遇到一个需要等待长时间执行的任务的时候,我们可以切换到其他的任务执行。

asyncio 异步模块

async与await关键字:

  • async:定义异步
  • await:执行异步

相关函数:

  • gather:将异步函数批量执行,返回一个列表,既函数执行结果的列表
  • run:执行主异步函数,返回值是函数执行的返回值
import time, random
def zulu():
    for i in range(10):
        print('Zulu {}'.format(i))
        time.sleep(random.random() * 2)
    return 'zulu'
def tango():
    for i in range(10):
        print('Tango {}'.format(i))
        time.sleep(random.random() * 2)
    return 'tango'
if __name__ == '__main__':
    start = time.time()
    zulu()
    tango()
    end = time.time()
    print('耗时:{}'.format(end-start))
复制代码

image.png

将同步改为异步执行的方式

async def zulu():
    for i in range(10):
        print('Zulu {}'.format(i))
        await asyncio.sleep(random.random() * 2)
    return 'zulu'
async def tango():
    for i in range(10):
        print('Tango {}'.format(i))
        await asyncio.sleep(random.random() * 2)
    return 'tango'
async def main():
    result = await asyncio.gather(
        zulu(),
        tango()
    )
    print(result)
if __name__ == '__main__':
    start = time.time()
    # zulu()
    # tango()
    asyncio.run(main())
    end = time.time()
    print('耗时:{}'.format(end-start))
复制代码

image.png

zulu()和tango()两个函数交互执行,时间缩短一半

分别在zulu函数中和mian函数中打印出pid。

image.png

与多线程和多进程编程模型相比,异步编程只是在同一个线程之内的的任务调度

gevent 异步模块

gevent异步包需要通过pip进行安装

python3 -m pip install gevent -i https://pypi.tuna.tsinghua.edu.cn/simple
复制代码

gevent 异步模块常用方法:

  • spawn:创建协程对象,参数为func以及传入函数的参数,返回一个协程对象
  • joinall:批量处理协程对象
  • get:获取函数返回结果
  • value:属性,也可以获取函数返回值
  • join:阻塞等待异步程序结束
  • kill:杀掉当前协程
  • dead:判断当前协程是否销毁
import time, random, os
import gevent
def zulu_gevent():
    for i in range(10):
        print('Zulu Gevent, PID:{}'.format(os.getpid()))
        gevent.sleep(random.random() ** 2)
    return 'Zulu with Gevent'
def tango_gevent():
    for i in range(10):
        print('Tango Gevent, PID:{}'.format(os.getpid()))
        gevent.sleep(random.random() ** 2)
    return 'Tango with Gevent'
if __name__ == '__main__':
    start = time.time()
    zulu = gevent.spawn(zulu_gevent)
    tango = gevent.spawn(tango_gevent)
    res_gevent = [zulu, tango]
    res = gevent.joinall(res_gevent)
    print(res)
    print(res[0].value)
    print(res[1].get())
    end = time.time()
    print('耗时:{}'.format(end - start))
    print('PID:{}'.format(os.getpid()))
复制代码

image.png

调用value属性可以从协程对象中获取函数的返回值。


相关文章
|
7天前
|
存储 数据挖掘 开发者
Python编程入门:从零到英雄
在这篇文章中,我们将一起踏上Python编程的奇幻之旅。无论你是编程新手,还是希望拓展技能的开发者,本教程都将为你提供一条清晰的道路,引导你从基础语法走向实际应用。通过精心设计的代码示例和练习,你将学会如何用Python解决实际问题,并准备好迎接更复杂的编程挑战。让我们一起探索这个强大的语言,开启你的编程生涯吧!
|
13天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的自我修养:从Python编程入门到深度学习实践
【10月更文挑战第39天】本文旨在为初学者提供一条清晰的道路,从Python基础语法的掌握到深度学习领域的探索。我们将通过简明扼要的语言和实际代码示例,引导读者逐步构建起对人工智能技术的理解和应用能力。文章不仅涵盖Python编程的基础,还将深入探讨深度学习的核心概念、工具和实战技巧,帮助读者在AI的浪潮中找到自己的位置。
|
13天前
|
机器学习/深度学习 数据挖掘 Python
Python编程入门——从零开始构建你的第一个程序
【10月更文挑战第39天】本文将带你走进Python的世界,通过简单易懂的语言和实际的代码示例,让你快速掌握Python的基础语法。无论你是编程新手还是想学习新语言的老手,这篇文章都能为你提供有价值的信息。我们将从变量、数据类型、控制结构等基本概念入手,逐步过渡到函数、模块等高级特性,最后通过一个综合示例来巩固所学知识。让我们一起开启Python编程之旅吧!
|
1天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
4天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
3天前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
7天前
|
存储 人工智能 数据挖掘
Python编程入门:打造你的第一个程序
本文旨在为初学者提供Python编程的初步指导,通过介绍Python语言的基础概念、开发环境的搭建以及一个简单的代码示例,帮助读者快速入门。文章将引导你理解编程思维,学会如何编写、运行和调试Python代码,从而开启编程之旅。
29 2
|
8天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
8天前
|
存储 Python
Python编程入门:理解基础语法与编写简单程序
本文旨在为初学者提供一个关于如何开始使用Python编程语言的指南。我们将从安装Python环境开始,逐步介绍变量、数据类型、控制结构、函数和模块等基本概念。通过实例演示和练习,读者将学会如何编写简单的Python程序,并了解如何解决常见的编程问题。文章最后将提供一些资源,以供进一步学习和实践。
19 1
|
11天前
|
存储 网络协议 IDE
从零起步学习Python编程
从零起步学习Python编程
下一篇
无影云桌面