Python常用模块详解
1、time模块
简单介绍
常用的一种获取当前时间以及时间格式化的模块,模块名称:time
time模块在Python原生安装中就存在所以不需要进行任何安装操作,直接使用即可。
导入方式:import time
常用方法
[注]常用时间函数
获取当前时间的时间戳 time.time()
获取当前时间:time.ctime()不传参数【可以传入一个时间戳,返回格式化后的时间】
获取当前时间的时间元祖:time.localtime() 接受时间戳
时间格式
时间元祖
2、Python 多进程多线程
基本概念
大家都知道计算机由CPU、内存、输入设备、输出设备组成。CPU主要是负责计算。而操作系统是计算机管理者,负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序侧是具有某种功能的程序,程序是运行于操作系统之上的。
进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离,进程之间的切换开销很大。我们每次执行py文件结束后会输出进程结束
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位.一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
进程和线程的区别:
(1)线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
(2)一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
(3)进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
(4)调度和切换:线程上下文切换比进程上下文切换要快得多。
总之,线程和进程都是一种抽象的概念,线程是一种比进程更小的抽象,线程和进程都可用于实现并发。
单线程:在单线程中,当处理器需要处理多个任务时,必须对这些任务安排执行顺序,并按照这个顺序来执行任务。假如我们创建了两个任务:听音乐(music)和看电影(movie)。在单线程中,我们只能按先后顺序来执行这两个任务
多线程
我们知道大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。多线程指的是多个任务同时进行。多线程一般用于IO型密集场景。在计算密集型的场景下,线程之间切换也是有消耗的。而且Python的GIL使得统一时间内只能有一个线程运行,因此若是想提高CPU利用率建议用多进程
3、Python多线程模块之thread和Threading模块
推荐使用threading模块,主要原因是, thread不支持守护线程。当主线程退出时,所有的子线程不管它们是否还在工作,都会被强行退出。有时我们并不希望发生这种行为,这时就引入了守护线程的概念。threading模块支持守护线程
threading使用简介-参数详解
threading.Thread(self, group=None, target=None,name=None, args=(), kwargs={})
参数group是预留的,用于将来扩展;
参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行;可调用对象比如传入方法名
参数name是线程的名字。默认值为“Thread-N“,N是一个数字。
参数args和kwargs分别表示调用target时的参数列表和关键字参数。比如调用方法的时候需要传入方法需要的参数
threading模块之Thread类常用方法与属性:
Thread.getName()/ Thread.name 用于获取线程的名称,有返回值,需要print打印出来
Thread.setName() 用于设置线程的名称,没有返回值,print打印出来事None
Thread.ident 获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。
Thread.is_alive() /Thread.isAlive 判断线程是否是激活的(alive)。从调用start()方法启动线程,到run()方法执行完毕或遇到未处理异常而中断这段时间内,线程是激活的。
Thread.join([timeout]) 调用Thread.join将会使主线程堵塞,直到被调用线程运行结束或超时。参数timeout是一个数值类型,表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束
代码如下
import threading
import time
def music(n):
for iin range(n):
print('this is music method;number is %s'%i)
time.sleep(1)
def dance(n):
for iin range(n):
print('this is dance method;number is %s'%i)
print('the thread name is %s'%threading.Thread.getName(t2))
print('time is %s'%time.ctime())
time.sleep(1)
t1 = threading.Thread(target=music,name='music', args=(20,))
t2 = threading.Thread(target=dance,name='dance', args=(10,))
t2.setName('dance02')
if __name__ =='__main__':
t1.start()
# t1.join()
t2.start()
# time.sleep(20)
print(threading.Thread.is_alive(t1))#rue
print(threading.Thread.is_alive(t2))#False
join方法详解
Thread.join([timeout]) 调用Thread.join将会使主线程堵塞,直到被调用线程运行结束或超时。参数timeout是一个数值类型,表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束
1.什么是子线程? 包含在 threading.Thread中,里面均视为子线程。
2.什么是主线程? 除了“不包含在Thread里面的程序”,UI界面和Main函数均为主线程,均可视为主线程
【注】Thread 是threading模块中最重要的类之一,可以使用它来创建线程。有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入。
守护线程:子线程设置成主线程的守护线程,那么主线程结束的时候会杀死子线程
GIL全局锁
首先多线程目的是为了提升代码执行效率,但是同时会引出一个问题。因为多线程之间是共享内存和数据的。所以要解决的一个问题是保证线程间数据一致性和状态同步。解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。于是有了GIL这把超级大锁GIL是全局解释锁,在Python解释器解释执行任何 Python 代码时,都需要先获得这把锁才行,GIL也不可避免的带来了一定的性能损失,在遇到 I/O 操作时会释放这把锁,如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行
GIL全局锁带来的问题:当CPU有多个核心的时候,问题就来了。从release GIL到acquireGIL之间几乎是没有间隙的。所以当其他在其他核心上的线程被唤醒时,大部分情况下主线程已经又再一次获取到GIL了。这个时候被唤醒执行的线程只能白白的浪费CPU时间,看着另一个线程拿着GIL欢快的执行着。然后达到切换时间后进入待调度状态,再被唤醒,再等待,以此往复恶性循环。GIL的存在导致多线程无法很好的利用多核CPU的并发处理能力。
加线程锁GIL全局锁是无法保证数据统一性的,在程序中如果需要保证两个子线程之间互不影响,需要加锁。
4、Python多进程模块multiprocessing
multiprocessing库的出现很大程度上是为了弥补thread库因为GIL低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,完全并行,无GIL的限制(进程中包括线程),可充分利用多cpu多核的环境,因此也不会出现进程之间的GIL争抢
简单介绍
python多进程并发,模块名称:multiprocessing
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程
借助这个包,可以轻松完成从单进程到并发执行的转换
导入方式: import multiprocessing
Multiprocessing使用简介
multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该Process对象与Thread对象的用法相同,也有start(), run(), join()等方法
此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的Thread类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境
multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源,而是线程)
进程创建
创建进程的类:Process()
【注】target是可调用对象,比如方法名,不能写成字符串
代码如下:
import time
import multiprocessing
class FunWork(multiprocessing.Process):
def __init__(self):
multiprocessing.Process.__init__(self)
print('begin')
def run(self):
for iin range(100):
time.sleep(1)
print('end %s'%i)
if __name__ =='__main__':
# 方式1,实例化multiprocessing的Process类,将可调用对象传进去
p1 = multiprocessing.Process(target=work)
p2 = multiprocessing.Process(target=work)
p1.start()
p2.start()
print(n)# 0
# 方式2:定义类,继承Process.
# 注意要重写run方法,在init初始化方法中调用父类的初始化方法 ,在调用start方法时候就会自定调用 重写后的run方法
pp1 = FunWork()
pp2 = FunWork()
pp1.start()
pp2.start()
方法:is_alive()、join([timeout])、run()、start() 。其中,Process以start()启动某个进程
pool进程池
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行它
进程池方法
apply(func[, args[, kwds]]): 阻塞的执行,比如创建一个有3个线程的线程池,当执行时是创建完一个,执行完函数再创建另一个,变成一个线性的执行.
apply_async(func[, args[, kwds[, callback]]]) : 它是非阻塞执行,同时创建3个线程的线城池,同时执行,只要有一个执行完立刻放回池子待下一个执行,并行的执行 .
close(): 关闭pool,使其不在接受新的任务。
terminate() : 结束工作进程,不在处理未完成的任务。
join():主进程阻塞,等待子进程的退出,join方法要在close或terminate之后使用
锁
进程间通信
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列Queue和管道Pipe,这两种方式都是使用消息传递的
Queue队列
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。 参数介绍:maxsize是队列中允许最大项数,省略则无大小限制
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常
操作
pipe管道
Pipe方法返回(conn1, conn2)代表一个管道的两个端。Pipe方法有duplex参数:duplex 为 True(默认值),那么这个管道是全双工模式,也就是说conn1和conn2均可收发。duplex为 False,conn1只负责接受消息,conn2只负责发送消息
send和recv方法分别是发送和接收消息的方法。在全双工模式下,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError