小白都看懂了,Python 中的线程和进程精讲,建议收藏

目录

线程和进程

一、 什么是进程 / 线程

1、 引论

众所周知,CPU是计算机的核心,它承担了所有的计算任务。而操作系统是计算机的管理者,是一个大管家,它负责任务的调度,资源的分配和管理,统领整个计算机硬件。应用程序是具有某种功能的程序,程序运行与操作系统之上

2、 线程

在很早的时候计算机并没有线程这个概念,但是随着时代的发展,只用进程来处理程序出现很多的不足。如当一个进程堵塞时,整个程序会停止在堵塞处,并且如果频繁的切换进程,会浪费系统资源。所以线程出现了

线程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。一个进程可以拥有多个线程,而且属于同一个进程的多个线程间会共享该进行的资源

干货主要有:

① 200 多本 Python 电子书(和经典的书籍)应该有

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且可靠的练手项目及源码)

④ Python基础入门、爬虫、网络开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

私信小编01即可获取大量Python学习资源

3、 进程

进程时一个具有一定功能的程序在一个数据集上的一次动态执行过程。进程由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时需要的数据和工作区;程序控制块(PCB)包含程序的描述信息和控制信息,是进程存在的唯一标志

4、 区别

一个进程由一个或者多个线程组成,线程是一个进程中代码的不同执行路线切换进程需要的资源比切换线程的要多的多进程之间相互独立,而同一个进程下的线程共享程序的内存空间(如代码段,数据集,堆栈等)。某进程内的线程在其他进程不可见。换言之,线程共享同一片内存空间,而进程各有独立的内存空间

5、 使用

在Python中,通过两个标准库 thread 和 Threading 提供对线程的支持, threading 对 thread 进行了封装。 threading 模块中提供了 Thread , Lock , RLOCK , Condition 等组件

二、 多线程使用

在Python中线程和进程的使用就是通过 Thread 这个类。这个类在我们的 thread 和 threading 模块中。我们一般通过 threading 导入

默认情况下,只要在解释器中,如果没有报错,则说明线程可用

from threading import Thread

1、 常用方法

Thread.run(self) # 线程启动时运行的方法,由该方法调用 target 参数所指定的函数

Thread.start(self) # 启动线程,start 方法就是去调用 run 方法

Thread.terminate(self) # 强制终止线程

Thread.join(self, timeout) # 阻塞调用,主线程进行等待

Thread.setDaemon(self, daemonic) # 将子线程设置为守护线程

Thread.getName(self, name) # 获取线程名称

Thread.setName(self, name) # 设置线程名称

2、 常用参数

参数

说明

target

表示调用对象,即子线程要执行的任务

name

子线程的名称

args

传入 target 函数中的位置参数,是一个元组,参数后必须添加逗号

3、 多线程的应用

3.1 重写线程法

import time, queue, threading

class MyThread(threading.Thread):

def __init__(self):

super().__init__()

self.daemon = True # 开启守护模式

self.queue = queue.Queue(3) # 开启队列对象,存储三个任务

self.start() # 实例化的时候直接启动线程,不需要手动启动线程

def run(self) -> None: # run方法线程自带的方法,内置方法,在线程运行时会自动调用

while True: # 不断处理任务

func, args, kwargs = self.queue.get()

func(*args, **kwargs) # 调用函数执行任务 元组不定长记得一定要拆包

self.queue.task_done() # 解决一个任务就让计数器减一,避免阻塞

# 生产者模型

def submit_tasks(self, func, args=(), kwargs={}): # func为要执行的任务,加入不定长参数使用(默认使用默认参数)

self.queue.put((func, args, kwargs)) # 提交任务

# 重写join方法

def join(self) -> None:

self.queue.join() # 查看队列计时器是否为0 任务为空 为空关闭队列

def f2(*args, **kwargs):

time.sleep(2)

print("任务2完成", args, kwargs)

# 实例化线程对象

mt = MyThread()

# 提交任务

mt.submit_tasks(f2, args=("aa", "aasd"), kwargs={"a": 2, "s": 3})

# 让主线程等待子线程结束再结束

mt.join()守护模式:

主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束

3.2 直接调用法

def f2(i):

time.sleep(2)

print("任务2完成", i)

lis = []

for i in range(5):

t = Thread(target=f2, args=(i,))

t.start() # 启动 5 个线程

lis.append(t)

for i in lis:

i.join() # 线程等待

4、 线程间数据的共享

现在我们程序代码中,有多个线程, 并且在这个几个线程中都会去 操作同一部分内容,那么如何实现这些数据的共享呢?

这时,可以使用 threading库里面的锁对象 Lock 去保护

Lock 对象的acquire方法 是申请锁

每个线程在操作共享数据对象之前,都应该申请获取操作权,也就是调用该共享数据对象对应的锁对象的acquire方法,如果线程A 执行了 acquire() 方法,别的线程B 已经申请到了这个锁, 并且还没有释放,那么 线程A的代码就在此处 等待 线程B 释放锁,不去执行后面的代码。

直到线程B 执行了锁的 release 方法释放了这个锁, 线程A 才可以获取这个锁,就可以执行下面的代码了

如:

import threading

var = 1

# 添加互斥锁,并且拿到锁

lock = threading.Lock()

# 定义两个线程要用做的任务

def func1():

global var # 声明全局变量

for i in range(1000000):

lock.acquire() # 操作前上锁

var += i

lock.release() # 操作完后释放锁

def func2():

global var # 声明全局变量

for i in range(1000000):

lock.acquire() # 操作前上锁

var -= i

lock.release() # 操作完后释放锁

# 创建2个线程

t1 = threading.Thread(target=func1)

t2 = threading.Thread(target=func2)

t1.start()

t2.start()

t1.join()

t2.join()

print(var)到在使用多线程时,如果数据出现和自己预期不符的问题,就可以考虑是否是共享的数据被调用覆盖的问题

使用 threading 库里面的锁对象 Lock 去保护

三、 多进程使用

1、 简介

Python中的多进程是通过multiprocessing包来实现的,和多线程的threading.Thread差不多,它可以利用multiprocessing.Process对象来创建一个进程对象。这个进程对象的方法和线程对象的方法差不多也有start(), run(), join()等方法,其中有一个方法不同Thread线程对象中的守护线程方法是setDeamon,而Process进程对象的守护进程是通过设置daemon属性来完成的

2、 应用

2.1 重写进程法

import time

from multiprocessing import Process

class MyProcess(Process): # 继承Process类

def __init__(self, target, args=(), kwargs={}):

super(MyProcess, self).__init__()

self.daemon = True # 开启守护进程

self.target = target

self.args = args

self.kwargs = kwargs

self.start() # 自动开启进程

def run(self):

self.target(*self.args, **self.kwargs)

def fun(*args, **kwargs):

print(time.time())

print(args[0])

if __name__ == '__main__':

lis = []

for i in range(5):

p = MyProcess(fun, args=(1, ))

lis.append(p)

for i in lis:

i.join() # 让进程等待守护模式:

主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束

2.2 直接调用法

import time

from multiprocessing import Process

def fun(*args, **kwargs):

print(time.time())

print(args[0])

if __name__ == '__main__':

lis = []

for i in range(5):

p = Process(target=fun, args=(1, ))

lis.append(p)

for i in lis:

i.join() # 让进程等待

3、 进程之间的数据共享

3.1 Lock 方法

其使用方法和线程的那个 Lock 使用方法类似

3.2 Manager 方法

Manager的作用是提供多进程共享的全局变量,Manager()方法会返回一个对象,该对象控制着一个服务进程,该进程中保存的对象运行其他进程使用代理进行操作

Manager支持的类型有:list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array语法:

from multiprocessing import Process, Lock, Manager

def f(n, d, l, lock):

lock.acquire()

d[str(n)] = n

l[n] = -99

lock.release()

if __name__ == '__main__':

lock = Lock()

with Manager() as manager:

d = manager.dict() # 空字典

l = manager.list(range(10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 启动10个进程,不同的进程对d和l中的不同元素进行操作

for i in range(10):

p = Process(target=f, args=(i, d, l, lock))

p.start()

p.join()

print(d)

print(l)

四、 池并发

1、 语法

线程池的基类是 concurrent.futures 模块中的 Executor , Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor ,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池

如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定

Exectuor 提供了如下常用方法:

submit(fn, *args, * kwargs):将 fn 函数提交给线程池。 args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。shutdown(wait=True):关闭线程池程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表

Future 提供了如下方法:

cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。cancelled():返回 Future 代表的线程任务是否被成功取消。running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数

2、 获取 CPU 数量

from multiprocessing import cpu_count # cpu核心数模块,其可以获取 CPU 核心数

n = cpu_count() # 获取cpu核心数

3、 线程池

使用线程池来执行线程任务的步骤如下:

调用 ThreadPoolExecutor 类的构造器创建一个线程池定义一个普通函数作为线程任务调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池from concurrent.futures import ThreadPoolExecutor

import threading

import time

# 定义一个准备作为线程任务的函数

def action(max):

my_sum = 0

for i in range(max):

print(threading.current_thread().name + ' ' + str(i))

my_sum += i

return my_sum

# 创建一个包含2条线程的线程池

pool = ThreadPoolExecutor(max_workers=2)

# 向线程池提交一个task, 50会作为action()函数的参数

future1 = pool.submit(action, 50)

# 向线程池再提交一个task, 100会作为action()函数的参数

future2 = pool.submit(action, 100)

def get_result(future):

print(future.result())

# 为future1添加线程完成的回调函数

future1.add_done_callback(get_result)

# 为future2添加线程完成的回调函数

future2.add_done_callback(get_result)

# 判断future1代表的任务是否结束

print(future1.done())

time.sleep(3)

# 判断future2代表的任务是否结束

print(future2.done())

# 查看future1代表的任务返回的结果

print(future1.result())

# 查看future2代表的任务返回的结果

print(future2.result())

# 关闭线程池

pool.shutdown() # 序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

也可以低于 CPU 核心数

3、 进程池

使用线程池来执行线程任务的步骤如下:

调用 ProcessPoolExecutor 类的构造器创建一个线程池定义一个普通函数作为进程程任务调用 ProcessPoolExecutor 对象的 submit() 方法来提交线程任务当不想提交任何任务时,调用 ProcessPoolExecutor 对象的 shutdown() 方法来关闭线程池关于进程的开启代码一定要放在 if __name__ == '__main__': 代码之下,不能放到函数中或其他地方

开启进程的技巧

from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor(max_workers=cpu_count()) # 根据cpu核心数开启多少个进程开启进程的数量最好低于最大 CPU 核心数

原文地址:https://www.toutiao.com/article/7070306676712342020/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Python中的函数(二) 在上一篇文章中提到了Python中函数的定义和使用,在这篇文章里我们来讨论下关于函数的一些更深的话题。在学习C语言函数的时候,遇到的问题主要有形参实参的区别、参数的传递和改变、变量的作用域。同样在Python中,关于对函数的理解和使用也存在这些问题。下面来逐一讲解。一.函
Python中的字符串 可能大多数人在学习C语言的时候,最先接触的数据类型就是字符串,因为大多教程都是以"Hello world"这个程序作为入门程序,这个程序中要打印的"Hello world"就是字符串。如果你做过自然语言处理方面的研究,并且用Python
Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程。下面就来了解一下如何在Python中进行对象编程。一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。 类是对现实世界中一些事物的封装,
Python面向对象编程(二) 在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Pytho
Python中的函数(一) 接触过C语言的朋友对函数这个词肯定非常熟悉,无论在哪门编程语言当中,函数(当然在某些语言里称作方法,意义是相同的)都扮演着至关重要的角色。今天就来了解一下Python中的函数用法。一.函数的定义 在某些编程语言当中,函数声明和函数定义是区分开的(在这些编程语言当中函数声明
在windows下如何快速搭建web.py开发框架 用Python进行web开发的话有很多框架供选择,比如最出名的Django,tornado等,除了这些框架之外,有一个轻量级的框架使用起来也是非常方便和顺手,就是web.py。它由一名黑客所创建,但是不幸的是这位创建者于2013年自杀了。据说现在由
将Sublime Text 2搭建成一个好用的IDE 说起编辑器,可能大部分人要推荐的是Vim和Emacs,本人用过Vim,功能确实强大,但是不是很习惯,之前一直有朋友推荐SUblime Text 2这款编辑器,然后这段时间就试了一下,就深深地喜欢上这款编辑器了...
Python中的模块 有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt这个函数,必须用语句"#include<math.h>"引入math.h这个头文件,否则是无法正常进行调用的。那么在Python中,如果要引用一些内置的函数,该怎么处理呢?在Python中
Python的基础语法 在对Python有了基础的认识之后,下面来了解一下Python的基础语法,看看它和C语言、java之间的基础语法差异。一.变量、表达式和语句 Python中的语句也称作命令,比如print "hello python"这就是一条语句。 表达式,顾名思义,是
Eclipse+PyDevʽjango+Mysql搭建Python web开发环境 Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。一.准备工作 需要下载的
在windows下安装配置Ulipad 今天推荐一款轻便的文本编辑器Ulipad,用来写一些小的Python脚本非常方便。 Ulipad下载地址: https://github.com/limodou/ulipad http://files.cnblogs.com/dolphin0520/u...
Python中的函数(三) 在前面两篇文章中已经探讨了函数的一些相关用法,下面一起来了解一下函数参数类型的问题。在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这个是严格进行规定的。然而在Python中函数参数定义和传递的方式相比而言就灵活多了。一.函数参数的
在Notepad++中搭配Python开发环境 Python在最近几年一度成为最流行的语言之一,不仅仅是因为它简洁明了,更在于它的功能之强大。它不仅能够完成一般脚本语言所能做的事情,还能很方便快捷地进行大规模的项目开发。在学习Python之前我们来看一下Python的历史由来,"Pytho
Python中的条件选择和循环语句 同C语言、Java一样,Python中也存在条件选择和循环语句,其风格和C语言、java的很类似,但是在写法和用法上还是有一些区别。今天就让我们一起来了解一下。一.条件选择语句 Python中条件选择语句的关键字为:if 、elif 、else这三个。其基本形式如
关于raw_input( )和sys.stdin.readline( )的区别 之前一直认为用raw_input( )和sys.stdin.readline( )来获取输入的效果完全相同,但是最近在写程序时有类似这样一段代码:import sysline = sys.stdin.readline()
初识Python 跟学习所有的编程语言一样,首先得了解这门语言的编程风格和最基础的语法。下面就让我们一起来了解一下Python的编程风格。1.逻辑行与物理行 在Python中有逻辑行和物理行这个概念,物理行是指在编辑器中实际看到的一行,逻辑行是指一条Python语句。在Python中提倡一个物理行只
当我们的代码是有访问网络相关的操作时,比如http请求或者访问远程数据库,经常可能会发生一些错误,有些错误可能重新去发送请求就会成功,本文分析常见可能需要重试的场景,并最后给出python代码实现。
1.经典迭代器 2.将Sentence中的__iter__改成生成器函数 改成生成器后用法不变,但更加简洁。 3.惰性实现 当列表比较大,占内存较大时,我们可以采用惰性实现,每次只读取一个元素到内存。 或者使用更简洁的生成器表达式 4.yield from itertools模块含有大量生成器函数可
本文介绍简单介绍socket的常用函数,并以python-kafka中的源码socketpair为例,来讲解python socket的运用
python实践中经常出现编码相关的异常,大多网上找资料而没有理解原理,导致一次次重复错误。本文对常用Unicode、UTF-8、GB2312编码的原理进行介绍,接着介绍了python字符类型unicode和str以及常见编解码错误UnicodeEncodeError和UnicodeDEcodeEr