目录
线程通信
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
运算方法:点击
进程和线程的区别和联系:
- 两者都是多任务编程的方式,都能够使用计算的多核
- 进程的创建删除要比线程消耗更多的的计算机资源
- 进程空间独立,数据安全性好,有专门的进程间通行方法
- 线程使用全局变量通信,更加简单,但是需要同步互斥操作
- 一个进程可以包含多个线程,线程共享进程的空间资源
- 进程线程都独立执行,有自己的特有资源如属性,ID,命令集等
使用情况:
- 一个进程中并发任务比较多,比较简单,适合使用多线程
- 如果数据程序比较复杂,特别是可能多个任务通信比较多的时候,要考虑到使用线程同步互斥的复杂性
- 多个任务存在明显差异,和功能分离的时候没有必要一定写入到一个程序中
- 使用python考虑线程GIL问题
要求:
- 进程线程区别和关系
- 进程间通信方式都用过哪些,有什么特点
- 同步和互斥是怎么回事,你都用哪些方法实现了同步互斥
- 什么是僵尸进程,怎么处理的
- python线程的效率怎么样?GIL是怎么处理的
服务器模型
硬件服务器:主机 集群
- 厂商:IBM HP 联想 浪潮
软件服务器:编写的服务端程序,依托硬件服务器运行,提供给用户一定的功能服务
- 分类:
webserver-->网站后端应用程序,提供数据处理和逻辑处理
httpserver-->接受http请求,返回http响应
邮箱服务器-->处理邮件请求,进行邮件收发
文件服务器-->提供文件的上传下载存储 - 功能实现:
网络连接,逻辑处理,数据运算,数据交互,协议实现,网络数据传输... - 模型结构:
C/S(客户端服务器模型)
B/S(浏览器服务器模型) - 服务器目标:处理速度更快,数据安全性更强,并发量更高
- 硬件:更高的配置,更好的技术硬件搭配,更高的网络速度,更多的主机,网络安全投入
- 软件:程序占有更少的资源,更稳当的运行效率,更流畅的速度,更强大的算法,更合理的技术搭配
网络服务器基础
循环服务器:单进程程序,循环接受客户请求,处理请求,处理完毕再接受下一个请求
- 特点:每次只能处理一个客户端请求
如果客户端长期占有服务器则无法处理其他客户端请求。 - 优点:实现简单,占有资源少
- 缺点:无法同时处理多客户端,体验差
- 使用情况:任务短暂,可以快速完成,udp比tcp更适合循环
并发服务器:能够同时处理多个客户端任务请求
- IO并发:IO多路复用,协程
- 优点:可以实现IO的并发操作,占用系统资源少
- 缺点:不能监控cpu密集的情况 并且不能长期阻塞
- 多进程/多线程并发:为每个客户端单独提供一个进程/线程处理客户端请求
- 优点:客户端可以长期占有服务器
- 缺点:消耗计算机资源较多
多进程并发模型
使用frok完成并发
- 创建套接字,绑定,监听
- 等待接受客户端连接请求
- 创建新的进程处理客户端请求
父进程继续等待连接其他客户端 - 客户端退出,对应子进程结束
示例:
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,技术分析:
- tcp套接字更适合文件传输
- 并发方案 -->fork 多进程并发
- 对文件的读写操作
- 获取文件列表 --> os.listdir()
粘包的处理
2,整体结构设计
- 服务器功能封装在类中(上传,下载,查看列表)
- 创建套接字,流程函数调用 main()
- 客户端负责发起请求,接受回复,展示
- 服务端负责接受请求,逻辑处理
3,编程实现
- 搭建整体结构,创建网络连接
- 创建多进程和类的结构
- 每个功能模块的实现
作业:
- 补充完整tftpserver的基本框架
- 尝试实现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()