(八)线程通信,服务器模型

线程通信
    通信方法 : 多个线程共用进程空间,所以进程的全局变量对进程内的线程均可见。因此使用全局变量通信是线程主要通信方法
    注意事项 :线程间通信更容易产生资源争夺,往往需要同步互斥机制做为通信的安全保证

线程的同步互斥

线程的Event事件

操作:
    e = threading.Event()
    e.wait([timeout]) 如果e为设置状态则不阻塞,未设置则阻塞
    e.set()  将e变为设置状态
    e.clear() 将e设置去除

import threading
from time import sleep
s = None
# 创建事件对象
e = threading.Event()
def bar():
    print("呼叫foo")
    global s
    s = "天王盖地虎"
def foo():
    print("等待口令")
    sleep(2)
    if s == "天王盖地虎":
        print("自己人")
    else:
        print("打死他")
    e.set()  # 将e变成设置状态
def fun():
    print("呵呵....")
    sleep(1)
    e.wait() #设置阻塞
    global s
    s = "小鸡炖蘑菇"
t1 = threading.Thread(target = bar)
t2 = threading.Thread(target = foo)
t3 = threading.Thread(target = fun)
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()

线程锁
    lock = threading.Lock() 创建锁
    lock.acquire()  上锁
    lock.release()  解锁
    操作原理:重复上锁,

import threading
a = b = 0
lock = threading.Lock()
def value():
    while True:
        lock.acquire()
        if a != b:
            print("a = %d,b = %d",(a,b))
        lock.release()
t = threading.Thread(target = value)
t.start()
while True:
    lock.acquire()
    a +=1
    b +=1
    lock.release()
t.join()


python线程的GIL问题(全局解释器锁)

python --> 支持多线 --> 同步互斥 --> 加锁 --> 超级锁(给解释器加锁)

后果:一个解释器,同一时刻只能解释一个线程。大大降低了python多线程的执行效率

python的GIL问题 解决方案
* 尽量使用进程
* 不使用c作为解释器
* python线程适用于高延迟的IO操作,网络操作,不适合CPU密集型或者传输速度很快的IO操作

注意:线程遇到阻塞会让出解释器

效率测试
Line cpu: 8.061699390411377
Line IO: 5.261598348617554

Thread cpu: 8.920610427856445
Thread io: 5.60230565071106

process cpu: 4.07219386100769
Process io: 3.383375406265259

结论:进程的效率明显提高,由于GIL影响线程效率较低

进程和线程的区别和联系
1.两者都是多任务编程的方式,都能够使用计算机多核资源
2.进程的创建和删除要比线程消耗更多计算机资源
3.进程空间独立,数据安全性好,有专门的进程间通信方法
4.线程使用全局变量通信,更加简单,但是往往要与同步互斥机制操作
5.一个进程可以包含多个线程,线程共享进程的资源
6.进线程都有自己的特有属性资源,如命令,属性,id等

使用场景 : 
* 需要创建较多的并发,任务比较简单时,线程比较合适
* 如果数据操作和功能比较独立,此时使用进程比较合适
* 使用线程时要考虑到同步互斥的复杂程度
* Python线程需要考虑到GIL问题

总结 : 
1. 进程线程的特征
2. 进程线程区别和关系
3. 同步互斥的意义,用过什么方法,什么情况下用
4. 进程间通信方式都知道哪些,有什么特点
5. 僵尸进程怎么处理,线程的GIL问题怎么看
6. 给一个情景,问选择进程还是线程,为什么

服务器模型

硬件服务器:主机 集群
厂商:IBM HP 联想 浪潮

软件服务器:编写的服务端程序,依托于硬件服务器运行,提供给用户一定的软件服务
分类:webserver  --> 网络后端程序提供网站请求的后端处理和响应
     httpserver --> 处理HTTP请求,回复http响应
     邮箱服务器 --> 处理邮件
     文件服务器 --> 处理文件传输

功能:网络连接,逻辑处理,数据的交互,数据的传输,协议的实现
模型结构: c/s (客户端服务器模型)
         b/s (浏览器服务端模型)

服务器目标:处理速度块,数据更安全,并发量大

硬件:更高的配置,集成分布基础,更好的网络速度,更多主机,更好的网路安全性
软件:程序占有更少的资源,更稳定的运行效果,更流畅的运行速度,采用更安全更合适的技术

基础的服务器模型

循环服务器:单进程程序,循环接受客户端请求,处理请求,每处理完一个请求再去接受下一个请求。
    优点:实现简单,占用资源少
    缺点:无法同时连接多个客户端,当一个客户端长期占有服务器时,形成其他客户端无法操作的情况
    使用情况:任务比较短暂,udp套接字更合适

并发服务器:同时处理多个客户端的任务请求
    IO并发:IO多路复用  协程
        优点:资源消耗少,效率较高,适用于IO类型服务器
        缺点:不能监控cpu密集型程序,本质是单进程,所以不能长期阻塞消息的收发
    多进程/多线程并发:为每一个客户端单独提供一个进程或者线程处理请求。由于进程线程执行独立,所以对其他进程不会有影响
        优点:客户端可以长期占有服务器,操作不会对其他进程线程产生影响
        缺点: 消耗资源较多

多进程并发

使用fork完成并发
1.创建套接字,绑定,监听
2.等待接受客户端请求 accept
3.创建子进程处理客户端请求,父进程继续等待其他客户端连接
4.客户端退出则子进程退出

from socket import *
import os,sys
import signal
#地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST,PORT)
#处理客户端请求函数
def client_handler(c):
    try:
        print("子进程接收客户端请求",c.getpeername())
        while True:
            data = c.recv(1024).decode()
            if not data:
                break
            print(data)
            c.send(b'Receive your message')
    except (KeyboardInterrupt,SystemExit):
        sys.exit(0)
    except Exception as e:
        print(e)
    c.close()
    sys.exit(0) #结束子进程
#创建套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
print("父进程%d等待客户端连接请求"%os.getpid())
# 处理僵尸
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
while True:
    try:
        c,addr = s.accept()
    except KeyboardInterrupt:
        sys.exit("服务器退出")
    except Exception as e:
        print(e)
        continue
    #创建子进程
    pid = os.fork()
    if pid < 0 :
        print("创建子进程失败")
        c.close()
        continue
    elif pid == 0:
        s.close()
        #处理客户端请求
        client_handler(c)
    else:
        c.close()
        continue


 

猜你喜欢

转载自blog.csdn.net/zh__quan/article/details/81087059