Python学习笔记(六)socketsever模块与线程

socketsever模块与线程

并发聊天

在之前的不间断聊天中,sk.listen(3)规定了等待的客户端最多有3个,但是只能有一个客户端在和服务端聊天,当前连接断开,等待队伍中的第一个客户端才能连接上服务端。如果我们需要服务端同时和几个客户端聊天该怎么做呢?可以使用socketsever来实现并发。由于每次建立连接的过程是类似的,所以被封装在 socketserver.BaseRequestHandler 中(默认listen为5),不需要自己实现,而代码的逻辑部分需要通过重写这个类中的 handle 方法来实现。最后,并发的实现通过 socketserver.ThreadingTCPServer 类来实现。参考代码:

# sever.py
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print("服务端启动...")
        while True:
            conn = self.request  # client传过来的 Socket
            print(self.client_address)
            while True:

                client_data = conn.recv(1024)

                print(str(client_data, "utf8"))
                print("waiting...")
                server_response = input(">>>")
                conn.sendall(bytes(server_response, "utf8"))

            conn.close()


if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8098), MyServer)
    server.serve_forever()
# =============================================================================
# client.py 不需要改动
import socket

ip_port = ('127.0.0.1', 8098)
sk = socket.socket()
sk.connect(ip_port)
print("客户端启动:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp, "utf8"))
    server_response = sk.recv(1024)
    print(str(server_response, "utf8"))
    if inp == 'exit':
        break
sk.close()

此时运行多个 client 都可以和 sever 进行聊天。


线程

说到并行,一定要提到线程,Python的线程和其他语言类似,举个例子:

import threading
import time

def func1(s):
    time.sleep(2)  # 花费两秒
    print('func1', s)

def func2(s):
    time.sleep(1)  # 花费一秒
    print('func2', s)

begin = time.time()

t1 = threading.Thread(target=func1, args=('first',))
t2 = threading.Thread(target=func2, args=('second',))
t1.start()
t2.start()
print('main...')

t1.join()
t2.join()

end = time.time()
print(end - begin)
# main...
# func2 second
# func1 first
# 2.0019052028656006

可以看出来,3个线程并驾齐驱,总共用时2秒钟。一个进程(process)可以有多个线程(threads),一般来说我们所写的比较简单的代码只有一个主线程。线程之间可以进行资源共享,但是进程之间不可以。

IO密集型任务有很多IO堵塞(可以理解为有很多时间在等待输入输出),上面的 time.sleep() 也属于IO堵塞,在IO密集型任务中不会一直使用CPU,所以在多线程中如果出现IO堵塞,CPU就去执行其他线程。而计算密集型任务会一直占用CPU,所以假设有两个线程都是计算累加和的函数,那么它们会互相抢占CPU,CPU在两个线程的切换中浪费了资源,时间上反而不如先执行一个函数,再执行另一个函数。当然如果CPU是多核的,就可以把两个任务分给两个CPU,这样就通过多线程并行来提高效率。但是这样的设想在Java可行,在Python还是会出现并行不如串行的现象,明明电脑是多核的,却看起来只能使用一个CPU,要解释这个问题需要了解Python的GIL。


Python的GIL

上面所提到的问题其实是CPython解释器的一个bug,GIL(Global Interpreter Lock) 全局解释器锁是CPython解释器上的一把锁,它规定了在同一时刻,只能有一个线程。这其实是一个历史遗留问题,可以说Python没有真正意义上的多线程,因为在Python不会出现两个CPU同时处理线程的情况。所以只能把事件放在不同进程中来执行以解决这个问题,这不是一个完美的解决方案。“协程 + 多进程”也是一种解决方案,相比于线程的抢占CPU,协程是用协商的方式来使用CPU。


同步

Java中的多线程同步通过对象锁来实现,在程序中用 synchronized(引用类型表达式) { 语句序列} 来表示。下面用一个买票程序来说明同步锁的问题,如果有100张票给10个窗口卖会出现以下情况:

import threading
num = 100  # 100张票
# lock = threading.Lock()
def sell_ticket(t):
    global num
    # lock.acquire()
    while num > 0:
        print(t, 'sell', num)
        num -= 1
    # lock.release()

threads = []
# 10个卖票窗口
for i in range(1, 11):
    threads.append(threading.Thread(target=sell_ticket, args=('t'+str(i),)))

for t in threads: t.start()
for t in threads: t.join()
print('final number:', num)

最后的结果是这样的:

会出现这种情况的原因是,有的票被卖了超过一次:

Python加锁通过treading.Lock 类实现,取消上面代码的注释语句就能让程序正确运行了。


参考:https://www.cnblogs.com/skyflask/articles/8283112.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值