[python全栈]06.多线程(3)

目录

  1. 信号通信
  2. 信号量
  3. 进程的同步互斥
  4. 线程
  5. 自定义线程类
  6. 线程的同步互斥
  7. 线程的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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值