目录
- 信号通信
- 信号量
- 进程的同步互斥
- 线程
- 自定义线程类
- 线程的同步互斥
- 线程的GIL问题
1. 信号通信
信号通信 :
一个进程向另一个进程发送信息来传递某种讯息
接受者根据接收到的信号进行相应的行为
系统信号 :
Linux $kill -l #查看系统信号
Linux $kill -sign PID #向PID号的进程发送信号
关于信号 :
SIGHUP 连接断开
SIGINT CTRL-C
SIGQUIT CTRL-\
SIGSTP CTRL-Z
SIGKILL 终止一个进程
SIGSTOP 暂停一个进程
SIGALRM 时钟信号
SIGCHLD 子进程状态改变时给父进程发出
python发送信号(signal模块)
import signal
发送信号方法 :
os.kill(pid,sig)
功能 : 发送信号
参数 : pid 目标进程
sig 要发送的信号
signal.alarm(sec) 非阻塞函数
功能 :向自身发送时钟信号 ---> SIGALRM
参数 : sec 时钟时间 / 收到时钟信号后进程终止
*进程中只能有一个时钟,第二个会覆盖第二个时钟
eg:
#alarm.py
import signal
import time
signal.alarm(3)
time.sleep(2)
signal.alarm(5)
while True:
time.sleep(1)
print("waiting for alarm...")
同步执行 : 按照顺序逐步执行,一步完成再做下一步
异步执行 : 在执行过程中利用内核记录延迟发生或者准备处理的事件
这样不影响应用层的持续执行
当事件发生时再由内核告知应用层处理
*信号是唯一的异步通信方法
signal.pause()
功能:阻塞等待接受一个信号
signal.signal(signum,handler)
功能 : 处理信号 (非阻塞函数&异步函数)
参数 : signum 要处理的信号
handler 信号的处理方法:
1.SIG_DFL 表示使用默认的方法处理
2.SIG_IGN 表示忽略这个信号
3.func 传入一个函数,表示用指定函数处理
def func(sig,frame) ---> sig:捕获到的信号(常用)
frame:信号对象
eg:
#signal1.py
import signal
from time import sleep
signal.alarm(5)
def func(sig,frame):
print('handled')
#signal.signal(signal.SIGALRM,signal.SIG_DFL)
#signal.signal(signal.SIGALRM,signal.SIG_IGN)
signal.signal(signal.SIGINT,signal.SIG_IGN) #忽略 ctrl-c
signal.signal(signal.SIGALRM,func)
while True:
sleep(2)
print("waiting alarm...")
2. 信号量
信号量(信号灯) :
原理 : 给定一个数量,对多个进程可见,且多个进程都可以操作
进程通过对数量多少的判断执行各自的行为
multiprocessing ---> Semaphore()
sem = Semaphore(num)
功能 : 创建信号量
参数 : 信号量初始值
返回 : 信号量对象
sem.get_value() 获取信号量值
sem.acquire() 将信号量减1,当信号量为0会阻塞
sem.release() 将信号量加1
#sem1.py
from multiprocessing import Process,Semaphore
from time import sleep
import os
#创建信号量
sem = Semaphore(3)
def fun():
print('process %d waiting Sem'%os.getpid())
sem.acquire()
print('process %d acquire Sem'%os.getpid())
sleep(3)
sem.release()
print('process %d release Sem'%os.getpid())
jobs = []
for i in range(4):
p = Process(target = fun)
jobs.append(p)
p.start()
for i in jobs:
i.join()
print(sem.get_value())
3. 进程的同步互斥
临界资源 :
多个进程或线程都能操作的共享资源
临界区 :
操作临界资源的代码段称为临界区
同步 :
同步是一种合作关系
为完成某个任务,多进程或多线程之间形成一种协调
按照约定去或条件执行操作临界资源
如 : 队列资源的收发,套接字的收发
互斥 :
互斥是一种竞争/制约关系
当一个进程或线程使用临界资源时进行上锁处理
当另一个进程使用时会阻塞等待,直到解锁后才能继续使用
如 :数据库中的表锁和行锁
Python中同步互斥机制 :
Event 事件
multiprocessing ---> Event
#创建事件对象
e = Event()
#设置事件阻塞
e.wait([timeout])
#事件设置 当事件被设置后,e.wait()不再阻塞
e.set()
#清楚设置 当事件设置被clear后,e.wait()又会阻塞
e.clear()
#事件状态判断
e.is_set()
#event2.py
from multiprocessing import Event,Process
from time import sleep
def wait_event():
print("p1想操作临界区")
e.wait()
print("p1开始操作临界区资源",e.is_set())
with open("file") as f:
print("p1: ",f.read())
def wait_event_timeout():
print("p2想操作临界区")
e.wait(3)
if e.is_set():
with open("file") as f:
print("p2: ",f.read())
else:
print("p2不能读取文件")
e = Event()
p1 = Process(target = wait_event)
p1.start()
p2 = Process(target = wait_event_timeout)
p2.start()
print("主进程操作")
with open("file","w") as f:
sleep(3)
f.write("I love China")
e.set()
print("释放临界区")
p1.join()
p2.join()
#>>>
主进程操作
p2想操作临界区
p1想操作临界区
p2不能读取文件
释放临界区
p1开始操作临界区资源 True
p1: I love China
Lock 锁
multiprocessing ---> lock
#创建对象
lock = Lock()
lock.acquire() #上锁 如果已经上锁状态,调用此函数会阻塞
lock.release() #解锁
with lock : 上锁
...
...
语句块结束,自动解锁
#lock1.py
from multiprocessing import Process,Lock
import sys
from time import sleep
def writer1():
lock.acquire()
for i in range(20):
sys.stdout.write("writer1:先向终端写入")
lock.release()
def writer2():
lock.acquire()
for i in range(20):
sys.stdout.write("writer2:先向终端写入")
lock.release()
lock = Lock()
p1 = Process(target = writer1)
p2 = Process(target = writer2)
p1.start()
p2.start()
p1.join()
p2.join()
4. 线程
线程 :
线程也是一种多任务编程方法
也可以利用计算机多核资源完成程序的并发执行
线程又被称为轻量级的进程
线程的特征 :
1. 线程是计算机多核分配的最小单位(进程是计算机资源分配的最小单位)
2. 一个进程可以包含多个线程
3. 线程也是一个运行的过程,消耗计算机资源,多个线程共享进程的资源和空间
4. 线程的创建删除消耗的资源都要远远小于进程
5. 多个线程之间执行互不干扰
6. 线程也有自己的特有属性,比如指令集,ID
python3 : threading 模块创建线程
t = threading.Thread()
功能 : 创建线程对象
参数: name 线程名称 default Thread-1
target 线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
返回值 : 线程对象
t.start() 启动线程,自动运行线程函数
t.join([timeout]) 回收线程
线程对象属性 :
t.is_alive() 查看线程状态
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
threading.currentThread() 获取当前线程对象
t.daemon 属性
默认情况主线程退出不会影响分支线程执行
如果设置为True,则分支线程随主线程退出
t.daemon = True / t.setDeamon(True)
判断状态: t.isDaemon() 返回Daemon设置情况
*该属性一般在start前设置,不会和join一起用
#daemon属性
#thread3.py
from threading import Thread
from time import sleep
def fun():
sleep(3)
print("test daemon")
t = Thread(target = fun)
t.daemon = True
print(t.isDaemon())
t.start()
print("=== Main Thread Over ===")
#创建线程
#thread1.py
import threading
from time import sleep
import os
a = 1
#线程函数
def music():
global a
print("THREAD:a= ",a)
a = 10000
for i in range(5):
sleep(2)
print("THREAD:play music",os.getpid())
#create thread obj
t = threading.Thread(target = music)
t.start()
for i in range(5):
sleep(1.5)
print("MAIN:play music",os.getpid())
t.join()
print("MAIN:a =",a)
#一个进程里的多线程pid相同
#THREAD a = 1 / MAIN a = 10000
#线程属性
#thread2.py
from threading import Thread,currentThread
from time import sleep
#线程函数
def fun(sec):
print("线程属性测试")
sleep(sec)
#线程对象的getName()属性获取名称
print("%s 线程结束" % currentThread().getName())
#创建线程
thr = []
for i in range(3):
t = Thread(name = 'th%d' % i,target = fun,args = (2,))
thr.append(t)
t.start()
#查看线程状态
print(t.getName(),"is alive:",t.is_alive())
#更改线程名称
thr[1].setName("ChangeName")
print(thr[1].name)
#回收线程
for j in thr:
j.join()
5. 自定义线程类
创建自己的进程类 :
步骤 :
1. 继承Thread
2. 加载Thread中的 __init__
3. 重写 run() 方法
#创建自己的线程类
#mythread.py
from threading import Thread
from time import sleep,ctime
class MyThread(Thread):
def __init__(self,target,args=(),
kwargs={},name = 'MyThread'):
super().__init__()
self.target = target
self.args = args
self.kwargs = kwargs
self.name = name
def run(self):
self.target(*self.args,**self.kwargs)
def player(song,sec):
for i in range(2):
print("Playing %s:%s"%(song,ctime()))
sleep(sec)
t = MyThread(target = player,args = ("凉凉",),
kwargs = {"sec":2})
t.start()
t.join()
6. 线程的同步互斥
线程通信
通信方法 :多个线程共享进程的空间
线程间通信使用全局变量完成
注意事项 :线程间使用全局变量往往要同步互斥机制
保证通信安全
线程的同步互斥方法 :
线程的 event
e = threading.Event() 创建事件对象
e.wait([timeout]) 若e为设置状态则不阻塞,否则阻塞
e.set() 将e变为设置状态
e.clear() 清除设置
线程锁
lock = threading.Lock() #创建锁对象
lock.acquire() 上锁
lock.release() 解锁
*也可以通过with上锁,上锁状态调用acquire()会阻塞
7. 线程的GIL问题
python 线程系统的 BUG ---> GIL 问题
GIL(全局解释器锁)
python ---> 支持多线程 ---> 产生同步和互斥 ---> 加锁 --->
超级锁(给解释器加锁) ---> 解释器同一时刻只能解释一个线程
后果 : 一个解释器同一时刻只能解释执行一个线程
所以导致python线程效率低下
但是当遇到IO阻塞时线程会主动让出解释器
因此python线程更加适合高延迟的IO程序并发
解决方法 :
1. 尽量使用多进程编程
2. 不适用 C 解释器 使用 C# java 编译的解释器
3. 尽量使用多种方案组合的方式进行并发操作,线程用作高延迟IO
复习 :
1. 总结进程线程差异
2. 复习网络编程基本内容
3. 司机和售票员的故事
* 创建父子进程分别代表司机和售票员
* 当售票员收到SIGINT信号,给司机发送SIGUSR1信号此 时司机打印"老司机开车了"
当售票员收到SIGQUIT信号,给司机发送SIGUSR2信号此时司机打印"车速有点快,系好安全带"
当司机捕捉到SIGTSTP信号,给售票员发送SIGUSR1,售票员打印"到站了,请下车"
* 到站后 售票员先下车,司机下车 (子进程先退出)
说明 : SIGINT SIGQUIT SIGTSTP从键盘发出
#driver.py
from multiprocessing import Process
import os
from signal import *
from time import sleep
def saler_handler(sig,frame):
if sig == SIGINT:
os.kill(os.getppid(),SIGUSR1)
elif sig == SIGQUIT:
os.kill(os.getppid(),SIGUSR2)
elif sig == SIGUSR1:
print("到站了,请下车")
os._exit(0)
def driver_handler(sig,frame):
if sig == SIGUSR1:
print("老司机,开车了")
elif sig == SIGUSR2:
print("车速有点快,系好安全带")
elif sig == SIGTSTP:
os.kill(p.pid,SIGUSR1)
#子进程代表售票员
def saler():
signal(SIGINT,saler_handler)
signal(SIGQUIT,saler_handler)
signal(SIGUSR1,saler_handler)
signal(SIGTSTP,SIG_IGN)
while True:
sleep(2)
print("Python带你去远方")
p = Process(target = saler)
p.start()
#父进程
signal(SIGUSR1,driver_handler)
signal(SIGUSR2,driver_handler)
signal(SIGTSTP,driver_handler)
signal(SIGINT,SIG_IGN)
signal(SIGQUIT,SIG_IGN)
p.join()