第七天,线程、协程、IO多路复用、socketserver

      即然说到适合python八线程的,那么怎么着的不合乎用python三十二线程呢?

1.3.2 看看PID的不同

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello',os.getpid())

if __name__ == '__main__':
    #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
    t1=Thread(target=work)
    t2=Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程pid',os.getpid())

    #part2:开多个进程,每个进程都有不同的pid
    p1=Process(target=work)
    p2=Process(target=work)
    p1.start()
    p2.start()
    print('主线程/主进程pid',os.getpid())


'''
hello 13552
hello 13552
主线程pid: 13552
主线程pid: 13552
hello 1608
hello 6324
'''

总结:可以见见,主进度下开启三个线程,每一个线程的PID都跟主进度的PID一样;而开七个经过,每一种进度都有两样的PID。

      
那么大家诚挚不可能用多线程,上下文切换是急需时间的,数据量太大,不可能承受。那里大家就要用到多进程+协程

4.6 线程队列queue

queue队列:使用import queue,用法与经过Queue一样。

queue下有三种队列:

  • queue.Queue(maxsize) 先进先出,先放进队列的数码,先被取出来;
  • queue.LifoQueue(maxsize) 后进先出,(Lifo 意为last in first
    out),后放进队列的多寡,先被取出来
  • queue.PriorityQueue(maxsize) 优先级队列,优先级越高优先取出来。

举例:
先进先出:

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

后进先出:

import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

优先级队列:

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

             
不是那一个样子的,python四线程一般用于IO密集型的先后,那么什么样叫做IO密集型呢,举个例证,比如说带有阻塞的。当前线程阻塞等待别的线程执行。

5.2 greenlet实现协程

greenlet是三个用C达成的协程模块,相比与python自带的yield,它能够使您在任意函数之间自由切换,而不需把那一个函数先证明为generator。

安装greenlet模块
pip install greenlet

from greenlet import greenlet
import time

def t1():
    print("test1,first")
    gr2.switch()
    time.sleep(5)
    print("test1,second")
    gr2.switch()

def t2():
    print("test2,first")
    gr1.switch()
    print("test2,second")

gr1 = greenlet(t1)
gr2 = greenlet(t2)
gr1.switch()


'''
输出结果:
test1,first
test2,first   #等待5秒
test1,second
test2,second
'''

能够在第1遍switch时传入参数

from greenlet import greenlet
import time
def eat(name):
    print("%s eat food 1"%name)
    gr2.switch(name="alex")
    time.sleep(5)
    print("%s eat food 2"%name)
    gr2.switch()

def play_phone(name):
    print("%s play phone 1"%name)
    gr1.switch()
    print("%s play phone 1" % name)

gr1 = greenlet(eat)
gr2 = greenlet(play_phone)
gr1.switch(name="egon")  #可以在第一次switch时传入参数,以后都不需要

注意:greenlet只是提供了一种比generator愈来愈方便的切换方式,仍旧没有缓解遭受I/O自动切换的题材,而单独的切换,反而会骤降程序的执行进程。那就须求利用gevent模块了。

      

四、锁

C:\Python27\python.exe D:/weixin/temp/yield_tmp.py
______________1
______________2
______________3
______________4
______________5
______________6
______________7
______________8
______________9
______________10
______________1
______________1
______________2
______________2
______________3
______________3
______________4
______________4
______________5
______________5
______________6
______________6
______________7
______________7
______________8
______________8
______________9
______________9
______________10
______________10

Process finished with exit code 0

五、协程

协程:是单线程下的产出,又称微线程、纤程,英文名:Coroutine协程是一种用户态的轻量级线程,协程是由用户程序自身决定调度的。

亟待强调的是:

1.
python的线程属于基本级其他,即由操作系统控制调度(如单线程一旦遭逢io就被迫交出cpu执行权限,切换其余线程运维)

  1. 单线程内打开协程,一旦碰到io,从应用程序级别(而非操作系统)控制切换

相比操作系统控制线程的切换,用户在单线程内决定协程的切换,优点如下:

1.
协程的切换开支更小,属于程序级其他切换,操作系统完全感知不到,因此尤其轻量级

  1. 单线程内就足以兑现产出的效益,最大限度地运用cpu。

要兑现协程,关键在于用户程序本人主宰程序切换,切换在此以前务必由用户程序本身保留协程上一遍调用时的动静,如此,每一遍重复调用时,能够从上次的职务继续执行

(详细的:协程拥有本人的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到别的地方,在切回到的时候,苏醒原先封存的寄存器上下文和栈)

实践结果:开了四个经过,各样进程下进行11个体协会程合作职务

① 、开启线程的二种方法

在python中拉开线程要导入threading,它与开启进度所急需导入的模块multiprocessing在选取上,有非常大的相似性。在接下去的使用中,就足以窥见。

同开启进度的三种格局一样:

      那里运用比较完善的第叁方协程包gevent

4.4 事件Event

同进度的相同

线程的一个首要特性是种种线程都是独自运作且状态不行预测。假若程序中的别的线程通过判断某些线程的事态来分明自身下一步的操作,那时线程同步难点就会变得那么些难办,为了缓解那一个题材我们采取threading库中的Event对象。

Event对象涵盖多个可由线程设置的信号标志,它同意线程等待某个事件的发生。在起来境况下,伊芙nt对象中的信号标志被装置为假。假如无线程等待一个伊夫nt对象,而那么些伊芙nt对象的标志为假,那么那个线程将会被
一向不通直至该
标志为真。一个线程如若将四个伊芙nt对象的信号标志设置为真,它将唤起全部等待这几个伊夫nt对象的线程。要是3个线程等待三个曾经被
设置 为确实伊夫nt对象,那么它将忽略那个事件,继续执行。

伊芙nt对象拥有部分方法:
event = threading.Event() #发出一个事件目的

  • event.isSet():返回event状态值;
  • event.wait():如果event.isSet() == False,将封堵线程;
  • event.set():设置event的状态值为True,全体阻塞池的线程进入就绪状态,等待操作系统中度;
  • event.clear():苏醒event的动静值False。

应用场景:

譬如,我们有五个线程必要一连数据库,大家想要在运营时确定保障Mysql服务正常,才让那八个工作线程去老是Mysql服务器,那么大家就足以选择threading.Event()编写制定来协调各种工作线程的接连操作,主线程中会去品尝连接Mysql服务,借使平常的话,触发事件,各工作线程会尝试连接Mysql服务。

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    print('\033[42m%s 等待连接mysql。。。\033[0m' %threading.current_thread().getName())
    event.wait()  #默认event状态为False,等待
    print('\033[42mMysql初始化成功,%s开始连接。。。\033[0m' %threading.current_thread().getName())


def check_mysql():
    print('\033[41m正在检查mysql。。。\033[0m')
    time.sleep(random.randint(1,3))
    event.set()   #设置event状态为True
    time.sleep(random.randint(1,3))

if __name__ == '__main__':
    event=Event()
    t1=Thread(target=conn_mysql) #等待连接mysql
    t2=Thread(target=conn_mysql) #等待连接myqsl
    t3=Thread(target=check_mysql) #检查mysql

    t1.start()
    t2.start()
    t3.start()


'''
输出如下:
Thread-1 等待连接mysql。。。
Thread-2 等待连接mysql。。。
正在检查mysql。。。
Mysql初始化成功,Thread-1开始连接。。。
Mysql初始化成功,Thread-2开始连接。。。
'''

注:threading.Eventwait主意仍可以接受3个过期参数,私下认可情形下,就算事件直接从未发出,wait方法会一向不通下去,而进入那么些超时参数之后,假如打断时间超越这几个参数设定的值之后,wait方法会重返。对应于上边的选择场景,假若mysql服务器一贯没有运转,大家期望子线程能够打字与印刷一些日志来不断提示大家当下从未一个足以接二连三的mysql服务,大家就足以设置那么些超时参数来达成那样的指标:

上例代码修改后如下:

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    count = 1
    while not event.is_set():
        print("\033[42m%s 第 <%s> 次尝试连接。。。"%(threading.current_thread().getName(),count))
        event.wait(0.2)
        count+=1
    print("\033[45mMysql初始化成功,%s 开始连接。。。\033[0m"%(threading.current_thread().getName()))

def check_mysql():
    print('\033[41m正在检查mysql。。。\033[0m')
    time.sleep(random.randint(1,3))
    event.set()
    time.sleep(random.randint(1,3))

if __name__ == '__main__':
    event=Event()
    t1=Thread(target=conn_mysql) #等待连接mysql
    t2=Thread(target=conn_mysql) #等待连接mysql
    t3=Thread(target=check_mysql) #检查mysql

    t1.start()
    t2.start()
    t3.start()

那般,大家就足以在等候Mysql服务运转的同时,看到工作线程太史在等待的情状。应用:连接池。

      pip  install    gevent

4.5 定时器timer

定时器,钦定n秒后执行某操作。

from threading import Timer

def hello():
    print("hello, world")

t = Timer(1, hello)  #1秒后执行任务hello
t.start()   # after 1 seconds, "hello, world" will be printed
#coding=utf-8
from multiprocessing import Process
import gevent
#from gevent import monkey; monkey.patch_socket()
#用于协程的了程序
def yield_execFunc(x):
    print('______________%s'%x)


#yield_clist决定协程的数量
#开始协程操作
def yield_start(yield_clist):
    task=[] #用来存储协程
    for i in yield_clist:
        task.append(gevent.spawn(yield_execFunc,i))

    gevent.joinall(task) #执行协程

if  __name__=="__main__":
    list1=[1,2,3,4,5,6,7,8,9,10] #元素个数决定开起的协程数量
    list2=[1,2,3,4,5,6,7,8,9,10]
    list3=[1,2,3,4,5,6,7,8,9,10]
    process_list =[list1,list2,list3] #元素个数决定进程数量
    for plist in process_list:
        p = Process(target=yield_start,args=(plist,))
        p.start()

1.1 直接使用利用threading.Thread()类实例化

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.start()

    print('主线程')

     
协程的概念很已经建议来了,但直到眼二零二零年才在一些语言(如Lua)中赢得广泛应用。

二、 Python GIL

GIL全称Global Interpreter
Lock
,即全局解释器锁。首先要求肯定的某个是GIL并不是Python的性状,它是在完毕Python解析器(CPython)时所引入的二个概念。就好比C++是一套语言(语法)标准,可是能够用不一样的编写翻译器来编写翻译成可实施代码。有名的编写翻译器例如GCC,INTEL
C++,Visual
C++等。Python也同等,同样一段代码能够经过CPython,PyPy,Psyco等不等的Python执行环境来进行。像其中的JPython就从未有过GIL。可是因为CPython是绝领先百分之三十三条件下私下认可的Python执行环境。所以在很多个人的定义里CPython正是Python,也就想当然的把GIL归咎为Python语言的症结。所以那里要先明了一点:GIL并不是Python的风味,Python完全能够不依靠于GIL

       今后有这么一项职分:需求从200W个url中获取数据?

4.1 同步锁

要求:对2个全局变量,开启100个线程,每一种线程都对该全局变量做减1操作;

不加锁,代码如下:

import time
import threading

num = 100  #设定一个共享变量
def addNum():
    global num #在每个线程中都获取这个全局变量
    #num-=1

    temp=num
    time.sleep(0.1)
    num =temp-1  # 对此公共变量进行-1操作

thread_list = []

for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有线程执行完毕
    t.join()

print('Result: ', num)

分析:以上程序开启100线程并不可能把全局变量num减为0,第一个线程执行addNum遇见I/O阻塞后相当的慢切换来下3个线程执行addNum,由于CPU执行切换的速度尤其快,在0.1秒内就切换完结了,那就招致了第二个线程在获得num变量后,在time.sleep(0.1)时,其他的线程也都得到了num变量,全数线程得到的num值都是100,所以最后减1操作后,正是99。加锁达成。

加锁,代码如下:

import time
import threading

num = 100   #设定一个共享变量
def addNum():
    with lock:
        global num
        temp = num
        time.sleep(0.1)
        num = temp-1    #对此公共变量进行-1操作

thread_list = []

if __name__ == '__main__':
    lock = threading.Lock()   #由于同一个进程内的线程共享此进程的资源,所以不需要给每个线程传这把锁就可以直接用。
    for i in range(100):
        t = threading.Thread(target=addNum)
        t.start()
        thread_list.append(t)

    for t in thread_list:  #等待所有线程执行完毕
        t.join()

    print("result: ",num)

加锁后,第②个线程得到锁后初阶操作,第二个线程必须等待第②个线程操作达成后将锁释放后,再与别的线程竞争锁,得到锁的线程才有权操作。那样就保持了数码的四平,可是拖慢了执行进程。
注意:with locklock.acquire()(加锁)与lock.release()(释放锁)的简写。

import threading

R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()

   

5.3 gevent达成协程

gevent是2个第③方库,能够轻松通过gevent完毕产出同步或异步编制程序,在gevent中用到的基本点是Greenlet,它是以C扩展模块方式接入Python的轻量级协程。greenlet一体运作在主程操作系统进程的里边,但它们被同盟式地调节和测试。境遇I/O阻塞时会自动切换义务。

注意:gevent有友好的I/O阻塞,如:gevent.sleep()和gevent.socket();但是gevent无法一向识别除自己之外的I/O阻塞,如:time.sleep(2),socket等,要想识别那个I/O阻塞,必须打2个补丁:from gevent import monkey;monkey.patch_all()

  • 急需先安装gevent模块
    pip install gevent

  • 创办二个体协会程对象g1
    g1 =gevent.spawn()
    spawn括号内先是个参数是函数名,如eat,前面可以有两个参数,能够是岗位实参或根本字实参,都以传给第二个参数(函数)eat的。

from gevent import monkey;monkey.patch_all()
import gevent

def eat():
    print("点菜。。。")
    gevent.sleep(3)   #等待上菜
    print("吃菜。。。")

def play():
    print("玩手机。。。")
    gevent.sleep(5)  #网卡了
    print("看NBA...")

# gevent.spawn(eat)
# gevent.spawn(play)
# print('主') # 直接结束

#因而也需要join方法,进程或现场的jion方法只能join一个,而gevent的joinall方法可以join多个
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])  #传一个gevent对象列表。
print("主线程")

"""
输出结果:
点菜。。。
玩手机。。。    
##等待大概3秒       此行没打印
吃菜。。。
##等待大概2秒          此行没打印
看NBA...
主线程
"""

注:上例中的gevent.sleep(3)是仿照的I/O阻塞。跟time.sleep(3)效用雷同。

同步/异步

import gevent
def task(pid):
    """
    Some non-deterministic task
    """
    gevent.sleep(0.5)
    print('Task %s done' % pid)

def synchronous():  #同步执行
    for i in range(1, 10):
        task(i)

def asynchronous(): #异步执行
    threads = [gevent.spawn(task, i) for i in range(10)]
    gevent.joinall(threads)

print('Synchronous:')
synchronous()   #执行后,会顺序打印结果

print('Asynchronous:')
asynchronous()  #执行后,会异步同时打印结果,无序的。

爬虫应用

#协程的爬虫应用

from gevent import monkey;monkey.patch_all()
import gevent
import time
import requests

def get_page(url):
    print("GET: %s"%url)
    res = requests.get(url)
    if res.status_code == 200:
        print("%d bytes received from %s"%(len(res.text),url))

start_time = time.time()
g1 = gevent.spawn(get_page,"https://www.python.org")
g2 = gevent.spawn(get_page,"https://www.yahoo.com")
g3 = gevent.spawn(get_page,"https://www.github.com")
gevent.joinall([g1,g2,g3])
stop_time = time.time()
print("run time is %s"%(stop_time-start_time))

上以代码输出结果:

GET: https://www.python.org
GET: https://www.yahoo.com
GET: https://www.github.com
47714 bytes received from https://www.python.org
472773 bytes received from https://www.yahoo.com
98677 bytes received from https://www.github.com
run time is 2.501142978668213

应用:
由此gevent完结单线程下的socket并发,注意:from gevent import monkey;monkey.patch_all()必然要放手导入socket模块从前,不然gevent不能够分辨socket的围堵。

服务端代码:

from gevent import monkey;monkey.patch_all()
import gevent
from socket import *

class server:
    def __init__(self,ip,port):
        self.ip = ip
        self.port = port


    def conn_cycle(self):   #连接循环
        tcpsock = socket(AF_INET,SOCK_STREAM)
        tcpsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
        tcpsock.bind((self.ip,self.port))
        tcpsock.listen(5)
        while True:
            conn,addr = tcpsock.accept()
            gevent.spawn(self.comm_cycle,conn,addr)

    def comm_cycle(self,conn,addr):   #通信循环
        try:
            while True:
                data = conn.recv(1024)
                if not data:break
                print(addr)
                print(data.decode("utf-8"))
                conn.send(data.upper())
        except Exception as e:
            print(e)
        finally:
            conn.close()

s1 = server("127.0.0.1",60000)
print(s1)
s1.conn_cycle()

客户端代码 :

from socket import *

tcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.connect(("127.0.0.1",60000))

while True:
    msg = input(">>: ").strip()
    if not msg:continue
    tcpsock.send(msg.encode("utf-8"))
    data = tcpsock.recv(1024)
    print(data.decode("utf-8"))

通过gevent实现产出两个socket客户端去老是服务端

from gevent import monkey;monkey.patch_all()
import gevent
from socket import *

def client(server_ip,port):
    try:
        c = socket(AF_INET,SOCK_STREAM)
        c.connect((server_ip,port))
        count = 0
        while True:
            c.send(("say hello %s"%count).encode("utf-8"))
            msg = c.recv(1024)
            print(msg.decode("utf-8"))
            count+=1
    except Exception as e:
        print(e)
    finally:
        c.close()

# g_l = []
# for i in range(500):
#     g = gevent.spawn(client,'127.0.0.1',60000)
#     g_l.append(g)
# gevent.joinall(g_l)

#上面注释代码可简写为下面代码这样。

threads = [gevent.spawn(client,"127.0.0.1",60000) for i in range(500)]
gevent.joinall(threads)

     
实际上,python在实践二十四线程的时候,是经过GIL锁,举办上下文切换线程执行,每一次真实唯有一个线程在运行。所以上面才说,没有真正落实多现程。

2.1 什么是全局解释器锁GIL

Python代码的进行由Python
虚拟机(也叫解释器主循环,CPython版本)来决定,Python
在布置之初就考虑到要在解释器的主循环中,同时唯有三个线程在进行,即在肆意时刻,唯有叁个线程在解释器中运作。对Python
虚拟机的造访由全局解释器锁(GIL)来支配,就是以此锁能保证平等时刻唯有一个线程在运转。
在八线程环境中,Python 虚拟机按以下措施执行:

  1. 设置GIL
  2. 切换成四个线程去运营
  3. 运行:
    a. 钦赐数量的字节码指令,只怕
    b. 线程主动让出控制(能够调用time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁GIL
  6. 再次重新以上全体手续

在调用外部代码(如C/C++扩充函数)的时候,GIL
将会被锁定,直到那个函数停止结束(由于在那之间从未Python
的字节码被周转,所以不会做线程切换)。

     
那里没有选用yield协程,那个python自带的并不是很周详,至于为什么有待于你去研讨了。

4.2 死锁与递归锁

所谓死锁:是指七个或八个以上的历程或线程在推行进度中,因争夺财富而导致的一种相互等待的风貌,若无外力功能,它们都将不能够推进下去。此时称系统处于死锁状态,或系统一发布出了死锁。那此永远在相互等待的进度称死锁进度

如下代码,就会发生死锁:

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()
        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()
        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''

消除死锁的措施

制止发出死锁的艺术便是用递归锁,在python中为了补助在同一线程中屡屡伸手同一财富,python提供了可重入锁RLock

这个RLock当中维护着二个Lock和1个counter变量,counter记录了acquire(获得锁)的次数,从而使得财富得以被一再require。直到七个线程全体的acquire都被release(释放)后,其余的线程才能取得能源。下边包车型大巴例子如若应用RLock代替Lock,就不会产生死锁的现象了。

mutexA=mutexB=threading.RLock()
#四个线程得到锁,counter加1,该线程内又遭逢加锁的情状,则counter继续加1,那时期全数其余线程都只能等待,等待该线程释放具有锁,即counter递减到0停止。

      那么如何是协程呢?

6.1 ThreadingTCPServer

ThreadingTCPServer完毕的Soket服务器内部会为种种client创造一个“线程”,该线程用来和客户端实行互动。

使用ThreadingTCPServer:

  • 成立三个接续自 SocketServer.BaseRequestHandler 的类
  • 类中务必定义八个称谓为 handle 的方法
  • 启动ThreadingTCPServer。
  • 启动serve_forever() 链接循环

服务端:

import socketserver

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        # print(addr)
        conn.sendall("欢迎致电10086,请输入1XXX,0转人工服务。".encode("utf-8"))
        Flag = True
        while Flag:
            data = conn.recv(1024).decode("utf-8")
            if data == "exit":
                Flag = False
            elif data == '0':
                conn.sendall("您的通话可能会被录音。。。".encode("utf-8"))
            else:
                conn.sendall("请重新输入。".encode('utf-8'))

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(("127.0.0.1",60000),MyServer)
    server.serve_forever()  #内部实现while循环监听是否有客户端请求到达。

客户端:

import socket

ip_port = ('127.0.0.1',60000)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024).decode("utf-8")
    print('receive:',data)
    inp = input('please input:')
    sk.sendall(inp.encode('utf-8'))
    if inp == 'exit':
        break
sk.close()

    我们一大半的时候利用四线程,以及多进度,可是python中由于GIL全局解释器锁的因由,python的三十二线程并从未真的落到实处

1.3.3 练习

练习一:选拔八线程,完成socket 并发连接
服务端:

from threading import Thread
from socket import *
import os

tcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcpsock.bind(("127.0.0.1",60000))
tcpsock.listen(5)

def work(conn,addr):
    while True:
        try:
            data = conn.recv(1024)
            print(os.getpid(),addr,data.decode("utf-8"))
            conn.send(data.upper())
        except Exception:
            break

if __name__ == '__main__':
    while True:
        conn,addr = tcpsock.accept()
        t = Thread(target=work,args=(conn,addr))
        t.start()

"""
开启了4个客户端
服务器端输出:
13800 ('127.0.0.1', 63164) asdf
13800 ('127.0.0.1', 63149) asdf
13800 ('127.0.0.1', 63154) adsf
13800 ('127.0.0.1', 63159) asdf

可以看出每个线程的PID都是一样的。
""

客户端:

from socket import *

tcpsock = socket(AF_INET,SOCK_STREAM)
tcpsock.connect(("127.0.0.1",60000))

while True:
    msg = input(">>: ").strip()
    if not msg:continue
    tcpsock.send(msg.encode("utf-8"))
    data = tcpsock.recv(1024)
    print(data.decode("utf-8"))

练习二:有多少个任务,3个接受用户输入,3个将用户输入的内容格式化成大写,3个将格式化后的结果存入文件。

from threading import Thread

recv_l = []
format_l = []

def Recv():
    while True:
        inp = input(">>: ").strip()
        if not inp:continue
        recv_l.append(inp)

def Format():
    while True:
        if recv_l:
            res = recv_l.pop()
            format_l.append(res.upper())

def Save(filename):
    while True:
        if format_l:
            with open(filename,"a",encoding="utf-8") as f:
                res = format_l.pop()
                f.write("%s\n" %res)

if __name__ == '__main__':
    t1 = Thread(target=Recv)
    t2 = Thread(target=Format)
    t3 = Thread(target=Save,args=("db.txt",))
    t1.start()
    t2.start()
    t3.start()

相关文章