python 线程通信 day08

目录

线程通信

python 线程的GIL问题

服务器模型


day08

线程通信

线程通信

  • 通信方法:多个线程共用进程空间,所以进程的全局变量对进程内线程均可见。线程往往使用全局变量进行通信
  • 注意事项:线程间使用全局变量进行通信,全局变量为共享资源,往往同步互斥机制
    示例:
    # three1.py
    from threading import Thread
    from time import sleep
    
    a = 1
    
    def foo():
        global a
        a = 1000
    
    def bar():
        sleep(1)
        print('a = ',a)  #a =  1000
    
    t1 = Thread(target = foo)
    t2 = Thread(target = bar)
    
    t1.start()
    t2.start()
    
    t1.join()
    t2.join()
    
    
    

线程的同步互斥

  线程Event

  • 创建对象
    e = threeding.Event()
  • 事件阻塞函数
    e.wait([timeout])
  • 设置事件
    e.set()
  • 清楚事件
    e.clear()
    示例:
    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()
    
    def fun():
        print('呵呵...')
        sleep(1)
        e.wait()
        global s
        s = '小鸡炖蘑菇'
    
    b = threading.Thread(target = bar)
    f = threading.Thread(target = foo)
    t = threading.Thread(target = fun)
    
    
    b.start()
    f.start()
    t.start()
    
    b.join()
    f.join()
    t.join()
    
    #呼叫foo
    #等口令
    #呵呵...
    #宝塔镇河妖
    

线程锁Lock

  • lock = threading.Lock()   创建锁
    lock.acquire()   上锁
    lock.release()   解锁
  • with lock          上锁
    操作原理 : 重复上锁 acquire()会阻塞
    示例:
    # tgredds.py
    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()
    
    #两个进程互不影响,所以不上锁会出现a != b

python 线程的GIL问题

GIL(全局解释器锁)

  • python --> 支持线程操作 --> IO的同步和互斥 --> 加锁 --> 超级锁(给解释器加锁)
  • 后果:一个解释器,同一时刻只能解释一个线程,此时其它线程需要等待,大大降低了python线程的执行效率

python GIL问题解决方案

  • 修改c解释器
  • 尽量使用多进程进行并行操作
  • python线程可以在高延迟多阻塞的IO情形
  • 不使用cpython    c#  java做解释器

效率测试

  分别测试  多进程  多线程  单进程执行相同的IO操作和CPU操作的事件

  运算时间:

  • Line cpu: 8.15166711807251

  • Line IO: 6.841825246810913

  • Thread cpu 8.414522647857666

  • Thread IO 6.023292541503906

  • Process cpu 4.079084157943726

  • Process IO 3.2132551670074463

   运算方法:点击

进程和线程的区别和联系:

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

使用情况:

  • 一个进程中并发任务比较多,比较简单,适合使用多线程
  • 如果数据程序比较复杂,特别是可能多个任务通信比较多的时候,要考虑到使用线程同步互斥的复杂性
  • 多个任务存在明显差异,和功能分离的时候没有必要一定写入到一个程序中
  • 使用python考虑线程GIL问题

要求

  1. 进程线程区别和关系
  2. 进程间通信方式都用过哪些,有什么特点
  3. 同步和互斥是怎么回事,你都用哪些方法实现了同步互斥
  4. 什么是僵尸进程,怎么处理的
  5. python线程的效率怎么样?GIL是怎么处理的

服务器模型

硬件服务器:主机  集群

  • 厂商:IBM  HP  联想  浪潮

软件服务器:编写的服务端程序,依托硬件服务器运行,提供给用户一定的功能服务

  • 分类:
    webserver-->网站后端应用程序,提供数据处理和逻辑处理
    httpserver-->接受http请求,返回http响应
    邮箱服务器-->处理邮件请求,进行邮件收发
    文件服务器-->提供文件的上传下载存储
  • 功能实现:
    网络连接,逻辑处理,数据运算,数据交互,协议实现,网络数据传输...
  • 模型结构:
    C/S(客户端服务器模型)
    B/S(浏览器服务器模型)
  • 服务器目标:处理速度更快,数据安全性更强,并发量更高
  • 硬件:更高的配置,更好的技术硬件搭配,更高的网络速度,更多的主机,网络安全投入
  • 软件:程序占有更少的资源,更稳当的运行效率,更流畅的速度,更强大的算法,更合理的技术搭配

网络服务器基础

  循环服务器:单进程程序,循环接受客户请求,处理请求,处理完毕再接受下一个请求

  • 特点:每次只能处理一个客户端请求
               如果客户端长期占有服务器则无法处理其他客户端请求。
  • 优点:实现简单,占有资源少
  • 缺点:无法同时处理多客户端,体验差
  • 使用情况:任务短暂,可以快速完成,udp比tcp更适合循环

  并发服务器:能够同时处理多个客户端任务请求

  • IO并发:IO多路复用,协程
    • 优点:可以实现IO的并发操作,占用系统资源少
    • 缺点:不能监控cpu密集的情况   并且不能长期阻塞
  • 多进程/多线程并发:为每个客户端单独提供一个进程/线程处理客户端请求
    • 优点:客户端可以长期占有服务器
    • 缺点:消耗计算机资源较多

多进程并发模型

  使用frok完成并发

  1. 创建套接字,绑定,监听
  2. 等待接受客户端连接请求
  3. 创建新的进程处理客户端请求
    父进程继续等待连接其他客户端
  4. 客户端退出,对应子进程结束
    示例:
    from socket import * 
    import os,sys 
    import signal 
    
    #创建套接字
    HOST = "0.0.0.0"
    PORT = 8888
    ADDR = (HOST,PORT)
    
    def client_handler(c):
        print("Connect from ",c.getpeername())
        try:
            while True:
                data = c.recv(1024).decode()
                if not data:
                    break 
                print(data)
                c.send(b"Receive your message")
        except (KeyboardInterrupt,SystemExit):
            sys.exit("退出进程")
        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("Parent process wait connect")
    #对僵尸进程处理
    signal.signal(signal.SIGCHLD,signal.SIG_IGN)
    
    while True:
        try:
            c,addr = s.accept()
        except KeyboardInterrupt:
            s.close()
            sys.exit("服务器退出")
        except Exception as e:
            print(e)
            continue
        #创建子进程处理客户端请求
        pid = os.fork()
    
        if pid == 0:
            s.close()
            #处理客户端请求
            client_handler(c)
        else:
            c.close()
            continue
    

    客户端:
     

    from socket import * 
    
    #创建套接字
    sockfd = socket()
    
    #发起连接
    sockfd.connect(('127.0.0.1',8888))
    
    while True:
        #消息收发
        msg = input("Msg>>")
        if not msg:
            break
        sockfd.sendall(msg.encode())
        data = sockfd.recv(1024)
        print(data.decode())
    
    sockfd.close()

  tftp文件服务器

  项目功能:

  • 客户端有简单的页面命令提示
  • 功能包含:
  1. 查看服务器文件库中的文件列表(普通文件)
  2. 可以下载其中的某个文件到本地
  3. 可以上传客户端文件到服务器文件库
  • 服务器需求:
  1. 允许多个客户端同时操作
  2. 每个客户端可能会连续发送命令

  1,技术分析:

  1. tcp套接字更适合文件传输
  2. 并发方案 -->fork 多进程并发
  3. 对文件的读写操作
  4. 获取文件列表 --> os.listdir()
    粘包的处理

  2,整体结构设计

  1. 服务器功能封装在类中(上传,下载,查看列表)
  2. 创建套接字,流程函数调用  main()
  3. 客户端负责发起请求,接受回复,展示
  4. 服务端负责接受请求,逻辑处理

  3,编程实现

  1. 搭建整体结构,创建网络连接
  2. 创建多进程和类的结构
  3. 每个功能模块的实现

作业:

  1. 补充完整tftpserver的基本框架
  2. 尝试实现get 或者put功能
    解析: 
​
from socket import *
import os 
import signal 
import sys 
import time 

#文件库
FILE_PATH = "/home/tarena/"

#实现功能模块
class TftpServer(object):
    def __init__(self,connfd):
        self.connfd = connfd 

    def do_list(self):
        #获取列表
        file_list = os.listdir(FILE_PATH)
        if not file_list:
            self.connfd.send("文件库为空".encode())
            return
        else:
            self.connfd.send(b'OK')
            time.sleep(0.1)

        files = ""
        for file in file_list:
            if os.path.isfile(FILE_PATH+file) and \
            file[0] != '.':
                files = files + file + '#'

        self.connfd.send(files.encode())


    def do_get(self,filename):
        try:
            fd = open(FILE_PATH + filename,'rb')
        except:
            self.connfd.send("文件不存在".encode())
            return
        self.connfd.send(b'OK')
        time.sleep(0.1)
        #发送文件
        try:
            while True:
                data = fd.read(1024)
                if not data:
                    break
                self.connfd.send(data)
        except Exception as e:
            print(e)
        time.sleep(0.1)
        self.connfd.send(b'##') #表示文件发送完成
        print("文件发送完毕")

    def do_put(self,filename):
        try:
            fd = open(FILE_PATH+filename,'wb')
        except:
            self.connfd.send("无法上传".encode())
            return 
        self.connfd.send(b'OK')
        while True:
            data = self.connfd.recv(1024)
            if data == b'##':
                break
            fd.write(data)
        fd.close()
        print("文件上传完毕")

#流程控制,创建套接字,创建并发,方法调用
def main():
    HOST = '0.0.0.0'
    PORT = 8888
    ADDR = (HOST,PORT)

    sockfd = socket()
    sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    sockfd.bind(ADDR)
    sockfd.listen(5)

    signal.signal(signal.SIGCHLD,signal.SIG_IGN)

    while True:
        try: 
            connfd,addr = sockfd.accept()
        except KeyboardInterrupt:
            sockfd.close()
            sys.exit("服务器退出")
        except Exception as e:
            print(e)
            continue
        print("客户端登录:",addr)

        #创建父子进程
        pid = os.fork()

        if pid == 0:
            sockfd.close()
            tftp = TftpServer(connfd)  # __init__传参
            while True:
                data = connfd.recv(1024).decode()
                if (not data) or data[0] == 'Q':
                    print("客户端退出")
                    sys.exit(0)
                elif data[0] == "L":
                    tftp.do_list()
                elif data[0] == 'G':
                    filename = data.split(' ')[-1]
                    tftp.do_get(filename)
                elif data[0] == 'P':
                    filename = data.split(' ')[-1]
                    tftp.do_put(filename)       
                else:
                    print("客户端发送错误指令")
        else:
            connfd.close()
            continue

if __name__ == "__main__":
    main()
from socket import *
import sys 
import time 

#实现各种功能请求
class TftpClient(object):
    def __init__(self,sockfd):
        self.sockfd = sockfd 

    def do_list(self):
        self.sockfd.send(b'L') #发送请求类型
        #接收服务器回应
        data = self.sockfd.recv(1024).decode()
        if data == "OK":
            data = self.sockfd.recv(4096).decode()
            files = data.split('#')
            for file in files:
                print(file)
            print("文件展示完毕")
        else:
            #请求失败原因
            print(data)


    def do_get(self,filename):
        self.sockfd.send(('G ' + filename).encode())
        data = self.sockfd.recv(1024).decode()
        if data == 'OK':
            fd = open(filename,'wb')
            while True:
                data = self.sockfd.recv(1024)
                if data == b'##':
                    break
                fd.write(data)
            fd.close()
            print("%s 下载完成\n"%filename)
        else:
            print(data)

    def do_put(self,filename):
        try:
            fd = open(filename,'rb')
        except:
            print("上传文件不存在")
            return 
        self.sockfd.send(("P "+filename).encode())
        data = self.sockfd.recv(1024).decode()
        if data == 'OK':
            while True:
                data = fd.read(1024)
                if not data:
                    break
                self.sockfd.send(data)
            fd.close()
            time.sleep(0.1)
            self.sockfd.send(b'##')
            print("%s 上传完毕"%filename)
        else:
            print(data)

#创建套接字建立连接
def main():
    if len(sys.argv) < 3:
        print("argv is error")
        return
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])
    ADDR = (HOST,PORT)

    sockfd = socket()
    sockfd.connect(ADDR)

    tftp = TftpClient(sockfd)   #__init__是否需要传参

    while True:
        print("")
        print("==========命令选项===========")
        print("**********  list  *********")
        print("********** get file  ******")
        print("********** put file  ******")
        print("**********  quit  *********")
        print("=============================")

        cmd = input("输入命令>>")

        if cmd.strip() == "list":
            tftp.do_list()
        elif cmd[:3] == "get":
            filename = cmd.split(' ')[-1]
            tftp.do_get(filename)
        elif cmd[:3] == "put":
            filename = cmd.split(' ')[-1]
            tftp.do_put(filename)
        elif cmd.strip() == "quit":
            sockfd.send(b'Q')
            sockfd.close()
            sys.exit("欢迎使用")
        else:
            print("请输入正确命令!")

if __name__ == "__main__":
    main()

猜你喜欢

转载自blog.csdn.net/qq_42584444/article/details/81698226
今日推荐