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属性可以从协程对象中获取函数的返回值。


相关文章
|
30天前
|
人工智能 数据可视化 数据挖掘
探索Python编程:从基础到高级
在这篇文章中,我们将一起深入探索Python编程的世界。无论你是初学者还是有经验的程序员,都可以从中获得新的知识和技能。我们将从Python的基础语法开始,然后逐步过渡到更复杂的主题,如面向对象编程、异常处理和模块使用。最后,我们将通过一些实际的代码示例,来展示如何应用这些知识解决实际问题。让我们一起开启Python编程的旅程吧!
|
29天前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。
|
17天前
|
Unix Linux 程序员
[oeasy]python053_学编程为什么从hello_world_开始
视频介绍了“Hello World”程序的由来及其在编程中的重要性。从贝尔实验室诞生的Unix系统和C语言说起,讲述了“Hello World”作为经典示例的起源和流传过程。文章还探讨了C语言对其他编程语言的影响,以及它在系统编程中的地位。最后总结了“Hello World”、print、小括号和双引号等编程概念的来源。
102 80
|
6天前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
27 14
|
12天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
93 2
|
16天前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
53 2
|
29天前
|
小程序 开发者 Python
探索Python编程:从基础到实战
本文将引导你走进Python编程的世界,从基础语法开始,逐步深入到实战项目。我们将一起探讨如何在编程中发挥创意,解决问题,并分享一些实用的技巧和心得。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考。让我们一起开启Python编程的探索之旅吧!
46 10
|
30天前
|
IDE 程序员 开发工具
Python编程入门:打造你的第一个程序
迈出编程的第一步,就像在未知的海洋中航行。本文是你启航的指南针,带你了解Python这门语言的魅力所在,并手把手教你构建第一个属于自己的程序。从安装环境到编写代码,我们将一步步走过这段旅程。准备好了吗?让我们开始吧!
|
29天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
29天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
50 3