异常
异常是python中比较基础的概念,在之前的代码中也有用到过,简而言之就是铺捕获代码中的异常,防止程序的中断。
断言:
#!/usr/bin/env python3
class DingyiException(Exception):
def __init__(self, massage):
self.massage = massage
def __str__(self):
return self.massage
a = 1
try:
assert a == 1 #判断条件是否为真。不为真则触发异常
except AssertionError :
print("Asser 出发的异常")
else:
print("如果没有异常执行这里")
finally:
print("不管有没有异常都执行这里")
自定义异常:
#!/usr/bin/env pyton3
class DingyiException(Exception): #自定义异常
def __init__(self, mas):
self.mas = mas
def __str__(self):
return self.mas
try:
raise DingyiException('我的异常') #出发异常
except DingyiException as e:
print(e)
线程与进程
进程的概念
程序并不能单独运行,只有将程序装载到内存中,系统为他分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,他是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发执行。正是这样的设计,大大的提高了cpu的使用率。进程的出现让每个用户感觉自己独享CPU,因此,进程就是为了在CPU上实现多道进程而提出的。
进程的缺点
进程的有点很多,但是也有缺点,主要有一下两点:
1、进程只能在一个时间干一件事,如果想要同时干两件或者多事,进程就无能为力了。
2、进程如果在执行的过程中阻塞。例如等待输入,整个进程就会被挂起,即使进程中有些工作不需要等待数据输入,也将无法执行
例如:在使用QQ聊天的时候,qq作为一个独立的进程如果同时只能干一件事,就无法实现同时发送、接收消息。
线程的概念
线程是操作系统能够进行运算调度的最小单位。它被包含在进程中之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程可以执行不同任务。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
线程与进程的区别
进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
线程与进程的区别可以归纳为以下4点:
1、地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2、通信:进程间通信需要进程同步和互斥手段的辅助,以保证数据的一致性。同一进程内的线程共享数据。
3、调度和切换:线程上下文切换比进程上下文切换要快得多。
4、在多线程OS中,进程不是一个可执行的实体。
Python GIL(Global Interpreter Lock)
python的GIL机制,是指python在同一时刻,只允许一个进程运行。这不是python自身的特性,造成这个的原因是因为python的编译器——CPython造成的。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython 就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
线程
threading模块
线程有两种调用模式
1、直接调用
#!/usr/bin/env python3
#直接调用实例实现多线程
import threading, time
def test(num):
print("This is the %s threading" % num)
time.sleep(2)
t1 = threading.Thread(target = test, args = (1,)) #生成一个线程实例,参数传入需要元组或列表
t2 = threading.Thread(target = test, args = (2,))
t1.start() #开始线程
t2.start()
print(t1.getName()) #显示线程的名称
print(t2.getName())
2、继承式调用
#!/usr/bin/env python3
#使用继承的形式实现多线程
import threading, time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self):
print("This is %s threading" % self.num)
time.sleep(2)
if __name__ == "__main__":
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
其他方法:
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run 线程被cpu调度后自动执行线程对象的run方法
等待线程结束
在调用多线程的时候,会有主线程比其他线程先执行完,导致其他线程没有执行完程序就退出的情况,这是就需要等待这些线程执行完毕后在结束主线程。
#!/usr/bin/env python3
#用调用的方式实现多线程,并等待所有的线程结束
import threading, time
def test(num):
print("This is %s threading" % num)
time.sleep(2)
thread_list = []
for i in range(10):
t = threading.Thread(target = test, args = (i,))
t.start()
thread_list.append(t)
for i in thread_list:
i.join() #等待每一个线程结束,如果放在start后面会使程序变成串行,这样处理就不会串行
for i in thread_list:
print(i.getName())
守护进程
#!/usr/bin/env python3
#守护进程
import threading, time
def run(num):
print("The thread%s start..." % num)
time.sleep(2)
print("The thread%s stop..." % num)
def main():
for i in range(5):
t = threading.Thread(target = run, args = (i,))
t.start()
print('This is starting', t.getName())
m = threading.Thread(target = main, args = ())
m.setDaemon(True) #将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务
m.start()
m.join(timeout = 3) #最多等3秒,要是过了三秒线程还没执行完,程序继续往下走
#time.sleep(3)
print("The main stop...")
注意:如果守护进程突然停止,它的资源(例如打开的文件,数据库等等)可能不会得带适当的释放。如果你希望这些线程可以得到正常的停止,可以使用一种信号机制,例如:event
线程锁
一个进程下可以调用多个进程,如果多个线程对同一个变量进行修改,就会使数据变乱得不到想要接结果,就需要线程锁去控制。
#!/usr/bin/env python3
# coding:utf8
#变量锁
import threading,time
def run():
global n
print("get n:", n)
#lock.acquire() #对n变量上锁
n -= 1
#lock.release() #对n变量解锁
n = 100
lock = threading.Lock() #添加锁
thread_list = []
for i in range(100):
t = threading.Thread(target = run)
t.start()
thread_list.append(t)
#for i in thread_list:
# i.join()
time.sleep(3)
print("n:",n)
GIL与锁
这里用户加的锁,更GIL机制没有关系,下图可以说明此问题
python可以自己加锁,这样就显得GIL功能变得多余了。Cpython加入GIL的原因是为了降低开发者的开发难度,不如在写python的时候不需要关心内存的回收问题,因为python的解释器会自动定期对内存进行回收。也可以理解为python解释器里有一个独立的线程,每过一段时间,就会全局轮询看看哪些内存数据是可以回收的,此时程序里的线程和python解释器里的线程是并发运行的,假设你的线程删除了一个变量,python解释器的垃圾回收线程在清空这个变量过程中的clearing时刻,可能一个其他的线程正好又重新给这个还没来得及清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,问了解决类似的问题,python解释器接单粗暴的加了锁,即当一个线程运行是,其他的线程都不能动,这样就解决了上述问题,这可以说是python早期版本的遗留问题。
递归锁
就是在锁的外面在套一个锁,如果不用RLock,会进入死循环
#!/usr/bin/env python3
#递归锁
import threading, time
def run1():
print("The run1 start...")
lock.acquire()
global num1
num1 += 1
lock.release()
return num1
def run2():
print("The run2 start...")
lock.acquire()
global num2
num2 += 1
lock.release()
return num2
def run3():
lock.acquire()
res1 = run1()
print("Between 1 and 2")
res2 = run2()
lock.release()
print(res1, res2)
if __name__ == "__main__":
num1, num2 = 0,0
lock = threading.RLock() #设置递归锁
for i in range(10):
t = threading.Thread(target = run3)
t.start()
while threading.active_count() != 1: #正在进行的线程,等于一表示其他的线程都结束了,只剩一个主线程
print("正在运行的进程:", threading.active_count()) #打印现在还剩多少个线程
else:
print("all threads done")
print("num1:%s, num2:%s" % (num1, num2))
信号量 semaphore
上面介绍的锁,是同时只允许一个线程更改数据,而semaphore是同时允许一定数量的线程更改数据。就比如上厕所,有三个位置,后面的人只能等有人出来了才能用。
#!/usr/bin/env python3
#信号量
import threading, time
def run(n):
xinhao.acquire()
time.sleep(1)
print("run the threading %s" % n)
xinhao.release()
if __name__ == "__main__":
num = 0
xinhao = threading.BoundedSemaphore(5) #最多允许允许5个线程同事运行
for i in range(20):
t = threading.Thread(target = run, args = (i,))
t.start()
while threading.active_count() != 1:
time.sleep(2)
print("正在运行的线程:",threading.active_count())
else:
print("all thread stop")
print(num)
计时器 time
表示在经过一段时候后再调用某个函数。
使用的方法和线程一样,使用start()
#!/usr/bin/env python3
import threading
def f():
print("hi lalalala")
t = threading.Timer(30.0, f) #等待30秒后执行
t.start()
事件驱动 event
event是一个简单的同步对象
event有一个内部的标志,线程可以等待这个标志,可以设置这个标志也可以删除这个标志
event = threading.Event()
客户端的线程可以等待标志
event.wait() 等待
服务端的线程可以设置或是清除标志:
event.set() 设置
event.clear() 清除
如果设置的标志,那么等待的那方就继续执行程序
如果标志被清除,程序就在设置等待的地方等待,直到标志被从新设置
任何数量的线程都可以等待同一个标志
用过event来实现两个或多个线程的交互
红绿灯例子
#!/usr/bin/env python3
#事件驱动
import threading, time, random
def light():
if not event.isSet():
event.set()
count = 0
while True:
if count <10:
print('\033[42;1m___green____\033[0m')
elif count <13:
print('\033[43;1m__yellow__\033[0m')
elif count <20:
if event.isSet():
event.clear()
print('\033[41;1m__red__\033[0m')
else:
count = 0
event.set()
time.sleep(1)
count += 1
def car(n):
while 1:
time.sleep(1)
if event.isSet():
print("the car %s running" %n)
else:
print("the car %s stop" %n)
event.wait()
if __name__ == "__main__":
event = threading.Event()
l = threading.Thread(target = light)
l.start()
for i in range(3):
t = threading.Thread(target = car, args = (i,))
t.start()
这里还有一个例子,员工进公司门要刷卡,我们这里设置一个线程是“门”,在设置集合线程为“员工”,员工看到门没打开,就刷卡,刷完卡,门开了,员工就可以通过了
#!/usr/bin/env python3
import threading, time, random
def door():
door_open_time_counter = 0
while True:
if door_swiping_event.is_set():
print("\033[32;1mdoor opening...\033[0m")
door_open_time_counter += 1
else:
print("\033[31;1mdoor closed..., swipe to open.\033[0m")
door_open_time_counter = 0
door_swiping_event.wait()
if door_open_time_counter > 3:
door_swiping_event.clear()
time.sleep(1)
def staff(n):
print("staff [%s] is comming..." % n)
while True:
if door_swiping_event.is_set():
print('\033[34;1mdoor is opened, passing...\033[0m')
break
else:
print("staff [%s] sees door got closed, swipping the card..." % n)
print(door_swiping_event.set())
door_swiping_event.set()
print("after set,", door_swiping_event.set())
time.sleep(0.5)
door_swiping_event = threading.Event() #设置事件
door_thread = threading.Thread(target = door)
door_thread.start()
for i in range(5):
p = threading.Thread(target = staff, args = (i,))
time.sleep(random.randrange(3))
p.start()
线程队列queues
队列在线程编程中有着很重要的作用,他可以使线程之间安全的交换信息
- 队列的种类:
- class queue.Queue(maxsize=0) #先入先出
- class queue.LifoQueue(maxsize=0) #后进先出
- class queue.PriorityQueue(maxsize=0) #存储数据时可设置队列的优先级
- 队列的结构:
- maxsize是一个整数,它设置了队列的最大长度,一旦到达长度,队列就会阻塞,直到队列中的项被取出后在开始继续向队列中添加数据。如果maxsize的设置小于等于0,队列的长度是无限大
- 优先级队列说明:
- 在设置优先级时,优先级的数字越小,优先级越大。传入数据的格式是(优先级,数据)
- 可能出现的报错:
- exception queue.Empty
- 表示队列已空,在没有阻塞的get()或是使用get_nowait()获取不到数据的时候会报错
- exception queue.Full
- 表示队列已满,在没有阻塞的put()或是使用put_nowait()无法上传数据时会报错
- Queue.qsize()
- 获取队列的长度
- Queue.empty()
- 如果队列为空,返回True,否则返回false
- Queue.full()
- 如果队列已满,返回True,否则返回false
- Queue.put(item, block=True, timeout=None)
- 向队列上传一个数据。如果block是True,timeout是None(默认),在队列满了的时候会阻塞,直到队列中有空闲。如果timeout是一个正数,在队列满了的时候,他会等待相应的秒数,如果时间到了对联还是满的就会引发Full报错。如果block是False,在put一个数据的时候,如果队列满了会立刻报错
- Queue.put_nowait(item)
- 不等待直接上传,队列满了会报错
- Queue.get(block=True, timeout=None)
- 获取队列中的数据,参数效果类比put()
- Queue.get_nowait()
- 类比Queue.put_nowait(item)
- Queue.task_done()
- 表明之前的队列任务已经完成。用于队列消费线程。
- 对每个使用get()的任务,task_done()对告诉队列任务已经完成。
- 如果join()正在阻塞,当所有的数据被处理后他将声明(意味着task_done()已经接收到被put()进队列的每个数据)
- 如果调用的次数多于队列原本的长度,会引发一个 ValueError
- Queue.join()
- block直到queue被消费完毕
- exception queue.Empty
#!/usr/bin/env python3
import queue
q = queue.Queue(maxsize = 5) #先进先出队列
class Test(object):
def __init__(self, n):
self.n = n
q.put(1) #队列放入数据
q.put(Test(10))
q.put([1,2,3,4.5],timeout = 3) #如果不设置,在队列满了之后会阻塞,等待队列有位置之后在放进去
q.put_nowait({'a':'asdasd'}) #不阻塞,在队列满了之后,会报错——queue.Full
print(q.get(timeout = 1)) #同out
print(q.get())
print(q.get())
print(q.get_nowait()) #在队列空了的时候会报错——queue.Empty
print("############################################")
q1 = queue.LifoQueue(maxsize=10) #先进后出的队列
q1.put(1)
q1.put(2)
q1.put(3,timeout = 2)
q1.put(4)
q1.put(5)
print(q1.get_nowait())
print(q1.get(timeout = 2))
print(q1.get())
print(q.empty()) #检查队列是否还有空的地方
print(q.full()) #检查对联是否满
print(q.qsize()) #返回队列长度
print("############################################")
q2 = queue.PriorityQueue() #带优先级的队列
q2.put((1,'asdda'), timeout = 3) #将优先级和内容挡在放在一个元组中
q2.put((50,'a阿萨德a')) #数字越小优先级越大
q2.put((20,[1,2,3,4,5]))
print(q2.get())
print(q2.get())
print(q2.get())
生产者消费者模型
- 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平和生产线程和消费线程的工作能力,来提高程序的整体处理数据的速度。
- 为什么要生产者消费者模式:
- 在线程世界里,生产者就是生产数据的线程,消费者就是消耗数据的线程。在多线程开发中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等消费者处理完,才能继续生产数据。反之,同样的道理。
- 什么是生产者消费者模式:
- 生产者消费者模式是通过一个容器来解决生产者和消费者一个强耦合问题。生产者胡消费者彼此之间不相互通信。所以生产者在生产完数据后不必等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
#!/usr/bin/env pyton3
#生产者消费者模型
import threading, queue, time
def consumer(name, q):
while True:
time.sleep(1)
print("consumer %s eat %s baozi" % (name, q.get()))
q.task_done()
def producer(name, q):
count = 1
while True:
print('producer cook %s%s baozi' % (name, count))
q.put(count)
count += 1
q.join()
print("there has noting")
q = queue.Queue()
c1 = threading.Thread(target = consumer, args = ('xiaowang',q))
c2 = threading.Thread(target = consumer, args = ('xiaoli',q))
c3 = threading.Thread(target = consumer, args = ('xiaoyue',q))
p1 = threading.Thread(target = producer, args = ("dahuang", q))
p2 = threading.Thread(target = producer, args = ("dasheng", q))
p3 = threading.Thread(target = producer, args = ("lalal", q))
c1.start()
c2.start()
#c3.start()
p1.start()
p2.start()
p3.start()
进程
multiprocessing模块
multiprocessing模块类似于童虎reading模块。multiprocessing模块通过调用子进程而不是线程有效的避开了GIL,由于这个原因,multiprocessing模块可以让程序员有效的利用CPU的多个核心,它在unix和Windows上都可运行
实现多进程
#!/usr/bin/env python3
#多进程
from multiprocessing import Process
import time
def f(name):
time.sleep(2)
print("name:", name)
if __name__ == "__main__":
for i in (1,2):
t = Process(target = f, args =(i,) )
t.start()
t.join()
#!/usr/bin/env python3
#体现多进程中,主进程和子进程
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
print('\n')
def f(name):
info('\033[31;1mfunction f \033[0m')
print('hello', name)
if __name__ == "__main__":
info("\033[32;1mmain process line\033]0m")
p = Process(target = info,args = ('dingyi',))
p.start()
p.join()
进程间的通讯
不同的进程之间内存是不共享的,想要实现两个进程间的数据交换,可以用以下方法:
Queues
使用方法跟threading里的queue差不多
#!/usr/bin/env python3
#多进程之间的通讯
from multiprocessing import Process, Queue
def f(q):
q.put("This is data")
if __name__ == "__main__":
q = Queue()
t = Process(target = f, args = (q,))
t2 = Process(target = f, args = (q,))
t.start()
t2.start()
print("from t", q.get())
print("from t2", q.get())
t.join()
t2.join()
Pipes
Pipe()返回一个链接对象的双向管道
#!/usr/bin/env python3
#通过管道让进程之间传递数据
from multiprocessing import Process,Pipe
def f(conn):
conn.send("hi,the data from child")
conn.close()
if __name__ == "__main__":
parent_conn, child_conn = Pipe() #生成管道
p = Process(target = f, args = (child_conn,))
p2 = Process(target = f, args = (child_conn,))
p.start()
p2.start()
print(parent_conn.recv()) #显示子进程发来的信息
print(parent_conn.recv()) #显示子进程发来的信息
p.join()
p2.join()
两个链接通过Pipe()成为一个双向管道的两端,每个链接都会有send()和recv()方法。注意:如果两个进程或线程同时读取或写入管道的同一端,管道中的数据有可能会被破坏。当然,在同一时间使用不同的管道不会出现不同的风险。
Manager
Manager可以实现不同进程之间数据的共享
一个manager对象可以通过Manager()控制一个server进程保存一个python对象,并允许其他进程访问这个python对象。
可以保存的对象类型有:list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array
#!/usr/bin/env python3
#通过manager实现进程间的数据共享
from multiprocessing import Process, Manager
def f(d, l):
d[1] = "1"
d['2'] = 2
d['sss'] = 'xxx'
l.append(123)
print(l)
if __name__ == "__main__":
with Manager() as manager:
d = manager.dict() #创建manager的字典
l = manager.list(range(10)) #创建manager的列表
p_list = []
for i in range(10):
p = Process(target = f, args=(d,l))
p.start()
p_list.append(p)
for i in p_list:
i.join()
print(d)
print(l)
Array
通过Array也可以实现数据的共享
from multiprocessing import Process,Array
temp = Array('i', [11,22,33,44])
def Foo(i):
temp[i] = 100+i
for item in temp:
print(i,'----->',item)
for i in range(2):
p = Process(target=Foo,args=(i,))
p.start()
进程同步(进程锁)
举个例子:多个进程都需要在屏幕输出信息,但是如果多个进程同时输出,那么屏幕就乱了,所以需要进程锁。
#!/usr/bin/env python3
# coding:utf8
#进程锁
from multiprocessing import Process,Lock
def f(l, i):
#l.acquire()
try:
print("This is %s threading" % i)
finally:
pass
#l.release()
if __name__ == "__main__":
l = Lock()
for i in range(100):
p = Process(target = f, args = (l, i))
p.start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用的进程为止。
进程池中有两个方法:
apply —>每次只允许一个进程运行
applo_async
#!/usr/bin/env python3
#进程池
from multiprocessing import Process, Pool
import time
def Foo(i):
time.sleep(2)
return i+100
def Bar(arg):
print('-->exec done:', arg)
pool = Pool(3)
for i in range(10):
pool.apply_async(func = Foo, args = (i,), callback = Bar) #callback是回调的意思,Foo返回的结果会自动传给Bar
print('end')
pool.close()
pool.join()
#进程池中的进程执行完毕后再关闭,如果注释,那么程序直接结束