一、概述

并发与多线程在任何编程言语中都对错常常用的,也对错常重要的,接下来就细讲python中的并发与多线程原理和实践,假如想了解python基础部分,可以参阅我以下几篇文章:

  • Python 介绍和环境准备
  • Python 基础语法介绍(一)
  • Python 基础语法介绍(二)
  • Python 高档编程之面向目标(一)
  • Python 高档编程之面向切面编程 AOP(二)

Python 高级编程之并发与多线程(三)

二、并发与并行原理

1)并行

当体系有一个以上CPU时,则进程的操作有可能非并发。当一个CPU履行一个进程时,另一个CPU可以履行另一个进程,两个进程互不抢占CPU资源,可以一起进行,这种办法咱们称之为并行

2)并发

当有多个进程在操作时,假如体系只要一个CPU,则它底子不可能真正一起履行一个以上的进程,说白了便是多个进程一起由同一个CPU履行,并发只能把CPU运转时间划分成若干个时间段,再将时间段分配给各个进程履行,在一个时间段的进程代码运转时,其它进程处于挂起状,这种办法咱们称之为并发

3)并发和并行差异

  • 并发和并行是即类似又有差异的两个概念,并行是指两个或许多个事件在同一时间一起履行,而并发是指两个或多个事件经过时间片轮番被履行
  • 在多道程序环境下,并发性是指在一段时间内微观上有多个程序在一起运转,但在单核CPU中,同一时间仅能有一道程序履行,故微观上这些程序只能是分时地交替履行
  • 倘若在核算机中有多个CPU,则这些可以并发履行的程序便可被分配到多个处理机上,完结并行履行,即利用每个处理机来处理一个可并发履行的程序,这样,多个程序便可以一起履行。

三、Python 多线程

1)进程与线程联系

  • 线程——线程是一个根本的CPU履行单元。它有必要依托于进程存活。一个线程是一个execution context(履行上下文),即一个CPU履行时所需求的一串指令。
  • 进程——进程是指一个程序在给定数据集合上的一次履行进程,是体系进行资源分配和运转调用的独立单位。可以简略地理解为操作体系中正在履行的程序。也就说,每个应用程序都有一个自己的进程。
  • 每一个进程启动时都会最早发生一个线程,即主线程。然后主线程会再创立其他的子线程。即进程由一个或多个线程组成。

两者的差异

  • 线程有必要在某个进程中履行。
  • 一个进程可包含多个线程,其间有且只要一个主线程。
  • 多线程同享同个地址空间、打开的文件以及其他资源。
  • 多进程同享物理内存、磁盘、打印机以及其他资源。

线程的因作用可以划分为不同的类型,大致可分为:

  • 主线程
  • 子线程
  • 看护线程(后台线程)
  • 前台线程

2)Python 多线程GIL介绍

其他言语,CPU是多核时是支持多个线程一起履行。但在Python中,无论是单核仍是多核,一起只能由一个线程在履行。其根源是GIL的存在。GIL的全称是Global Interpreter Lock(大局解说器锁),来历是Python规划之初的考虑,为了数据安全所做的决议。某个线程想要履行,有必要先拿到GIL,咱们可以把GIL看作是“通行证”,而且在一个Python进程中,GIL只要一个。拿不到通行证的线程,就不答应进入CPU履行。

而现在Python的解说器有多种,例如:

  • CPython:CPython是用C言语完结的Python解说器。 作为官方完结,它是最广泛运用的Python解说器。

  • PyPy:PyPy是用RPython完结的解说器。RPython是Python的子集, 具有静态类型。这个解说器的特点是即时编译,支持多重后端(C, CLI, JVM)。PyPy旨在提高功用,一起保持最大兼容性(参阅CPython的完结)。

  • Jython:Jython是一个将Python代码编译成Java字节码的完结,运转在JVM (Java Virtual Machine) 上。别的,它可以像是用Python模块一样,导入并运用任何Java类。

  • IronPython:IronPython是一个针对 .NET 框架的Python完结。它可以用Python和 .NET framework的库,也能将Python代码暴露给 .NET框架中的其他言语。

重点注意事项:

  • GIL只在CPython中才有,而在PyPy和Jython中是没有GIL的。

  • 每次开释GIL锁,线程进行锁竞赛、切换线程,会耗费资源。这就导致打印线程履行时长,会发现耗时更长的原因。

  • 而且因为GIL锁存在,Python里一个进程永远只能一起履行一个线程(拿到GIL的线程才能履行),这便是为什么在多核CPU上,Python 的多线程功率并不高的底子原因。

3)Python 创立多线程

Python供给两个模块进行多线程的操作,分别是threadthreading(常用),前者是比较初级的模块,用于更底层的操作,一般应用等级的开发不常用。

1、thread

直接运用threading.Thread()。示例如下:

import threading
# 这个函数名可随便界说
def run(n):
    print("current task:", n)
if __name__ == "__main__":
    t1 = threading.Thread(target=run, args=("thread 1",))
    t2 = threading.Thread(target=run, args=("thread 2",))
    t1.start()
    t2.start()

2、threading(常用)

继承threading.Thread来自界说线程类,重写run办法。示例如下:

import threading
class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数有必要要写
        self.n = n
    def run(self):
        print("current task:", self.n)
if __name__ == "__main__":
    t1 = MyThread("thread 1")
    t2 = MyThread("thread 2")
    t1.start()
    t2.start()

4)看护线程

看护线程便是主线程履行完,子线程不论有没有履行完,都会跟着主线程完毕。前期python版别可以这样设置:setDaemon(True),新版别这样设置:t.daemon=True,默许为False。

示例如下:

import threading
import time
def count(n, c):
    while c > 0:
        c -= 1
        print("线程:{} 在履行\n".format(n))
        time.sleep(1)
if __name__ == "__main__":
    t1 = threading.Thread(target=count, args=("t1", 3))
    t2 = threading.Thread(target=count, args=("t2", 3))
    t1.daemon = True
    t2.daemon = True
    t1.start()
    t2.start()
    # 将 t1 和 t2 加入到主线程中
    #t1.join()
    #t2.join()
    # 主线程
    print("主线程履行!")

履行成果如下:

线程:t1 在履行
线程:t2 在履行
主线程履行!

发现子线程并没有履行完就完毕了。

5)线程兼并(join)

join函数履行次序是逐一履行每个线程,履行完毕后继续往下履行。主线程完毕后,子线程还在运转,join函数使得主线程等到子线程完毕时才退出。示例如下:

import threading
import time
def count(n, c):
    while c > 0:
        c -= 1
        print("线程:{} 在履行\n".format(n))
        time.sleep(1)
if __name__ == "__main__":
    t1 = threading.Thread(target=count, args=("t1", 3))
    t2 = threading.Thread(target=count, args=("t2", 3))
    t1.start()
    t2.start()
    # 将 t1 和 t2 加入到主线程中
    #t1.join()
    #t2.join()
    # 主线程
    print("主线程履行!")

先来看把join去掉之后的输出效果如下:

主线程履行!
线程:t1 在履行
线程:t2 在履行
线程:t1 在履行
线程:t2 在履行
线程:t1 在履行
线程:t2 在履行

加上join之后,效果如下:

线程:t1 在履行
线程:t2 在履行
线程:t2 在履行
线程:t1 在履行
线程:t1 在履行
线程:t2 在履行
主线程履行!

【结论】

  • 看护线程(t1.daemon)——便是主线程完毕,子线程不论有没有履行完都会跟着主线程完毕。
  • 线程兼并(join)——主线程会等候一切子线程履行完之后再继续继续主线程。

6)线程同步与互斥锁

线程之间数据同享的。当多个线程对某一个同享数据进行操作时,就需求考虑到线程安全问题。threading模块中界说了Lock 类,供给了互斥锁的功用来确保多线程情况下数据的正确性。

用法的根本过程:

#创立锁
mutex = threading.Lock()
#确定
mutex.acquire([timeout])
#开释
mutex.release()

其间,确定办法acquire可以有一个超时时间的可选参数timeout。假如设定了timeout,则在超时后经过回来值可以判别是否得到了锁,然后可以进行一些其他的处理。具体用法见示例代码:

import threading
import time
num = 0
mutex = threading.Lock()
class MyThread(threading.Thread):
    def run(self):
        global num 
        time.sleep(1)
        if mutex.acquire(1):  
            num = num + 1
            msg = self.name + ': num value is ' + str(num)
            print(msg)
            mutex.release()
if __name__ == '__main__':
    for i in range(5):
        t = MyThread()
        t.start()

输出成果:

Thread-3: num value is 1
Thread-2: num value is 2
Thread-5: num value is 3
Thread-1: num value is 4
Thread-4: num value is 5

7)可重入锁(递归锁)

RLock 可重入锁是指同一个锁可以屡次被同一线程加锁而不会死锁。 完结可重入锁的目的是防止递归函数内的加锁行为,或许某些场景内无法获取锁A是否已经被加锁,这时假如不运用可重入锁就会对同一锁屡次重复加锁,导致当即死锁

假如是一把互斥锁(threading.Lock()),那么下面的代码会发生堵塞:

import threading
lock = threading.Lock()
lock.acquire()
    for i in range(10):
        print('获取第二把锁')
        lock.acquire()
        print(f'test.......{i}')
        lock.release()
    lock.release()

输出成果:

获取第二把锁

发现履行上面的代码,会出现死锁情况。这是因为获取第2次锁的时分需求等候开释锁,导致死锁情况了。

再来看运用RLock的示例:

import threading
lock = threading.RLock()
lock.acquire()
for i in range(10):
    print('获取第二把锁')
    lock.acquire()
    print(f'test.......{i}')
    lock.release()
lock.release()

输出成果:

获取第二把锁
test.......0
获取第二把锁
test.......1
获取第二把锁
test.......2
获取第二把锁
test.......3
获取第二把锁
test.......4
获取第二把锁
test.......5
获取第二把锁
test.......6
获取第二把锁
test.......7
获取第二把锁
test.......8
获取第二把锁
test.......9

可能咱们大部分人都知道,RLock其实底层维护了一个互斥锁和一个计数器,那互斥锁和计数器到底是怎么工作的?

当一个线程经过acquire()获取一个锁时,首先会判别拥有锁的线程和调用acquire()的线程是否是同一个线程,假如是同一个线程,那么计数器+1,函数直接回来(return 1),假如两个线程不一致时,那么会经过调用底层锁(_allocate_lock())进行堵塞自己(也可能是取得锁)。

8)守时器

假如需求规则函数在多少秒后履行某个操作,需求用到Timer类。具体用法如下:

from threading import Timer
def show():
    print("Pyhton")
if __name__ == "__main__":
    # 指定一秒钟之后履行 show 函数
    # 等候1s履行show函数
    t = Timer(1, show)
    t.start()

四、Python 多进程

Python要进行多进程操作,需求用到muiltprocessing库,其间的Process类跟threading模块的Thread类很类似。所以直接看代码熟悉多进程。

1)创立多进程

1、直接运用Process

示例如下:

from multiprocessing import Process
def show(name):
    print("Process name is " + name)
if __name__ == "__main__": 
    proc = Process(target=show, args=('subprocess',))  
    proc.start()  
    proc.join()

输出成果:

Process name is subprocess

2、继承Process来自界说进程类,重写run办法

示例如下:

from multiprocessing import Process
import time
class MyProcess(Process):
    def __init__(self, name):
        super(MyProcess, self).__init__()
        self.name = name
    def run(self):
        print('process name :' + str(self.name))
        time.sleep(1)
if __name__ == '__main__':
    for i in range(3):
        p = MyProcess(str(i))
        p.start()
        p.join()

输出成果:

process name :0
process name :1
process name :2

2)多进程通讯

进程之间不同享数据的。假如进程之间需求进行通讯,则要用到Queue模块或许Pipe模块来完结。

1、Queue

Queue是多进程安全的行列,可以完结多进程之间的数据传递。它首要有三个函数put()get()empty()

  • put()用以刺进数据到行列中,put还有两个可选参数:blockedtimeout

    • 假如可选的参数block为True且timeout为空目标(默许的情况,堵塞调用,无超时)。
    • 假如timeout是个正整数,堵塞调用进程最多timeout秒,假如一向无空空间可用,抛出Full反常(带超时的堵塞调用)。
    • 假如block为False,假如有闲暇空间可用将数据放入行列,不然当即抛出Full反常。
    • 其非堵塞版别为put_nowait等同于put(item, False)。
  • get()可以从行列读取而且删去一个元素。同样get有两个可选参数:blockedtimeout

    • 假如blocked为True(默许值),而且 timeout为正值,那么在等候时间内没有取到任何元素,会抛出Queue.Empty反常。
    • 假如blocked为False,有两种情况存在,假如Queue有一个值可用,则当即回来该值,不然,假如行列为空,则当即抛出Queue.Empty反常。
  • empty() 假如行列为空,回来True,反之回来False 示例如下:

from multiprocessing import Process, Queue
def put(queue):
    queue.put('Queue 用法')
if __name__ == '__main__':
    queue = Queue()
    pro = Process(target=put, args=(queue,))
    pro.start()
    print(queue.get())   
    pro.join()

2、Pipe

多进程还有一种数据传递办法叫做管道(Pipe),和Queue相类似。Pipe可以在进程之间创立一条管道,并回来元组(con1,con2)。其间,con1,con2表明管道两头的衔接目标。这儿要注意,有必要在发生Process目标之前发生管道,具体用法如下:

  • send(obj):经过衔接发送目标obj

  • recv():接纳con2.send(obj)所发送的目标。假如没有音讯可接纳,recv办法会一向堵塞。假如接纳的一端已经封闭衔接,则抛出EOFError

  • close():封闭衔接。假如con1被垃圾收回,将主动调用此办法。

  • fileno():回来衔接运用的整数文件描述符

  • poll([timeout]):假如衔接上的数据可用,回来True。timeout为指定等候的最长时限,若timeout缺省,办法当即回来成果,不再等候。若timeout值为None,则操作将无限制等候数据到来。

  • send_bytes(buffer[,offset[,size]]):经过衔接发送字节数据缓冲区,buffer是支持缓冲区接口的恣意目标,offset是缓冲区中的字节偏移量,size是要发生的字节数。成果以单条音讯的形式宣布,然后运用recv_bytes()进行接纳。

from multiprocessing import Process, Pipe
def show(conn):
    conn.send('Pipe 用法')
    conn.close()
if __name__ == '__main__':
    parent_conn, child_conn = Pipe() 
    pro = Process(target=show, args=(child_conn,))
    pro.start()
    print(parent_conn.recv())   
    pro.join()

【温馨提示】调用Pipe()回来管道的两头的Connection,因此, Pipe仅仅适用于只要两个进程一读一写的单双工情况,也便是说信息是只向一个方向活动。例如电视、广播,看电视的人只能看,电视台是能广播电视节目。

【总结】

  • Pipe的读写功率要高于Queue。
  • 进程间的Pipe根据fork机制树立。
  • 当主进程创立Pipe的时分,Pipe的两个Connections衔接的的都是主进程。 当主进程创立子进程后,Connections也被复制了一份。此刻有了4个Connections。
  • 尔后,封闭主进程的一个Out Connection,封闭一个子进程的一个In Connection。那么就树立好了一个输入在主进程,输出在子进程的管道。

3)进程池

创立多个进程,咱们不用傻傻地一个个去创立。咱们可以运用Pool模块来搞定。Pool 常用的办法如下:

办法 含义
apply() 同步履行(串行)
apply_async() 异步履行(并行)
terminate() 马上封闭进程池
join() 主进程等候一切子进程履行完毕。有必要在close或terminate()之后运用
close() 等候一切进程完毕后,才封闭进程池

示例如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import multiprocessing
import time
def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")
if __name__ == "__main__":
    # 保持履行的进程总数为processes,当一个进程履行完毕后会添加新的进程进去
    pool = multiprocessing.Pool(processes = 3)
    for i in range(5):
        msg = "hello %d" %(i)
        # 非堵塞式,子进程不影响主进程的履行,会直接运转到 pool.join()
        pool.apply_async(func, (msg, ))   
        # 堵塞式,先履行完子进程,再履行主进程
        # pool.apply(func, (msg, ))   
    print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
    # 调用join之前,先调用close函数,不然会出错。
    pool.close()
    # 履行完close后不会有新的进程加入到pool,join函数等候一切子进程完毕
    pool.join()   
    print("Sub-process(es) done.")

输出成果:

Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~
msg: hello 0
msg: hello 1
msg: hello 2
end
msg: hello 3
end
msg: hello 4
end
end
end
Sub-process(es) done.
  • 如上,进程池Pool被创立出来后,即便实践需求创立的进程数远远大于进程池的最大上限,p.apply_async(test) 代码依旧会不停的履行,并不会停下等候;相当于向进程池提交了10个恳求,会被放到一个行列中;
  • 当履行完p1 = Pool(5)这条代码后,5条进程已经被创立出来了,仅仅还没有为他们各自分配使命,也便是说,无论有多少使命,实践的进程数只要5条,核算机每次最多5条进程并行。
  • 当Pool中有进程使命履行完毕后,这条进程资源会被开释,pool会按先进先出的准则取出一个新的恳求给闲暇的进程继续履行;
  • 当Pool一切的进程使命完结后,会发生5个僵尸进程,假如主线程不完毕,体系不会主动收回资源,需求调用join函数去收回。
  • join函数是主进程等候子进程完毕收回体系资源的,假如没有join,主程序退出后不论子进程有没有完毕都会被强制杀死;
  • 创立Pool池时,假如不指定进程最大数量,默许创立的进程数为体系的内核数量。

4)多线程和多进程怎么选择?

在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种:CPU密集型和I/O密集型。

  • CPU 密集型(核算型):程序比较偏重于核算,需求常常运用CPU来运算。例如科学核算的程序,机器学习的程序等。(最好运用进程,因为python现在同一时间只能由一个线程在履行,具体原因看上面解说)

  • I/O 密集型:顾名思义便是程序需求频频进行输入输出操作。爬虫程序便是典型的I/O密集型程序。(最好运用线程,因为涉及到网络、磁盘IO的使命都是IO密集型使命,这类使命的特点是CPU耗费很少。)

五、Python 之 signal 模块

signal 模块负责python程序内部的信号处理;典型的操作包括信号处理函数、暂停并等候信号,以及守时宣布SIGALRM等。

虽然signal是python中的模块,但是首要针对UNIX渠道(比如Linux,MAC OS),而Windows内核中因为对信号机制的支持不充分,所以在Windows上的Python不能发挥信号体系的功用。

1)发生信号的原因

发送信号一般有两种原因:

  • 被动式:内核检测到一个体系事件.例如子进程退出会像父进程发送SIGCHLD信号.键盘按下control+c会发送SIGINT信号。
  • 主动式:经过体系调用kill来向指定进程发送信号。

2)信号处理办法

接纳信号的进程对不同的信号有三种处理办法:

  • 指定处理函数
  • 疏忽
  • 根据体系默许值处理, 大部分信号的默许处理是中止进程

3)规则信号

Python 高级编程之并发与多线程(三)
常用的信号:

signal.SIGHUP   # 衔接挂断;
signal.SIGILL   # 不合法指令;
signal.SIGINT   # 中止进程(ctrl+c);
signal.SIGTSTP  # 暂停进程(ctrl+z);
signal.SIGKILL  # 杀死进程(此信号不能被捕获或疏忽);
signal.SIGQUIT  # 终端退出;
signal.SIGTERM  # 中止信号,软件中止信号;
signal.SIGALRM  # 闹钟信号,由signal.alarm()发起;
signal.SIGCONT  # 继续履行暂停进程;

【温馨提示】

  • 因为不同体系中同一个数值对应的信号类型不一样, 所以最好运用信号名称。
  • wnidows体系中只能调用 SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM
  • 信号的数值越小, 优先级越高。

3)signal.alarm(time)

  • 参数:time为时间参数

  • 功用:在time时间后,向进程本身发送SIGALRM信号

示例如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 注意:在UNIX渠道上履行,window没有alarm()办法
import signal
import time
signal.alarm(4)#4s后中止程序
while True:
    time.sleep(1)
    print("学习python中...")

输出成果:

学习python中...
学习python中...
学习python中...
Alarm clock

4)signal.pasue()

signal.pause() Wait until a signal arrives。让进程进程暂停,以等候信号(什么信号均可);也即堵塞进程进行接纳到信号后使进程中止

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import signal
import time
time.sleep(1)
#堵塞等候信号的发生,如论什么信号都可以
signal.pause()
while True:
    time.sleep(1)
    print("学习python中...")

5)设置信号处理函数

signal.signal(sig, handler)

功用:依照handler拟定的信号处理计划处理函数

参数:

  • sig:拟需处理的信号,处理信号只针对这一种信号起作用sig

  • hander:信号处理计划

在信号基础里提到,进程可以无视信号、可采纳默许操作、还可自界说操作;当handler为下列函数时,将有如下操作:

  • SIG_IGN:信号被无视(ignore)或疏忽

  • SIG_DFL:进程选用默许(default)行为处理

示例如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import signal
#6s后中止程序
signal.alarm(6)
#遇到SIGINT ctrl+c时,疏忽SIG_IGN
signal.signal(signal.SIGINT,signal.SIG_IGN)
signal.pause()

运转后6s 打印出: Alarm clock 假如在运转中在键盘中输入CTRL+C也杯水车薪,此刻输出成果 ^C^C^C^C^C^C^C^C闹钟

原因剖析:

  • signal.signal(signal.SIGINT, signal.SIG_IGN) 表明遇到信号SIGINT CTRL + C,时,疏忽SIG_IGN该信号。所以在程序运转中从键盘输入ctrl+c(在终端上显现 ^C )时无效。

  • 当signal.alarm(6)计时6秒后,直接在终端上输出 “Alarm clock” 后退出。

  • signal.pause()是为了堵塞进程,等候信号。假如没有这句话,可以在程序中更变为:

while True:
    pass

进程中默许信号办法处理:

import signal
#6s后中止程序
signal.alarm(6)
signal.signal(signal.SIGALRM,signal.SIG_DFL)
signal.pause()

六、并发网络通讯模型

常见网络模型:

  • 循环服务器模型——循环接受客户端恳求,处理恳求.同一时间只能处理一个恳求,处理完毕后在处理下一个。

    • 优势:完结简略,占用资源少服务器。
    • 缺陷:无法一起处理多个客户端恳求网。
    • 适用情况:处理的使命可以很快完结,客户端无需长期占用服务端程序.UDP比TCP更适合循环多线程。
  • 多进程/线程网络并发模型——每当一个客户端链接服务器,就树立一个新的进程/线程为该客户端服务,客户端退出时在毁掉该进程/线程。

    • 优势:能一起知足多个客户端长期占有服务端需求,可以处理各类恳求并发。
    • 缺陷:资源耗费较大异步。
    • 适用情况:客户端一起链接量较少,需要处理行为较复杂场景。
  • IO并发模型——利用IO多路复用,异步IO等技能,一起处理多个客户端IO恳求

    • 优势:资源耗费少,能一起高效处理多个IO行为。
    • 缺陷:只能处理并发发生的IO事件,无法处理CPU核算函数。
    • 适用情况:HTTP恳求,网络传输等都是IO行为。

1)根据fork的多进程网络并发模型

完结过程:

  1. 树立监听套接字;
  2. 等候接受客户端恳求;
  3. 客户端链接树立新的进程处理客户端恳求;
  4. 原进程继续等候其余客户端链接;
  5. 若是客户端退出,则毁掉对应的进程。

示例如下:

import socket
import signal
import os
# 大局变量
HOST = "127.0.0.1"
PORT = 9090
ADDR = (HOST, PORT)
def dispose(val):
    while True:
        data = val.recv(1024)
        if not data:
            break
        print(">>", data.decode())
        val.send(b"OK")
    val.close()
# 创立套接字
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口当即重用
soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定套接字
soc.bind(ADDR)
# 设置监听
soc.listen(5)
# 处理僵尸进程
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
print("Listen the port 9090...")
while True:
    # 循环处理客户端衔接
    try:
        c, addr = soc.accept()
        print("Connect from", addr)
    except KeyboardInterrupt:
        os._exit(0)
    except Exception as e:
        print(e)
        continue
    # 创立子进程处理客户端事物
    pid = os.fork()
    if pid == 0:
        soc.close()
        # 客户端套接字处理具体事物
        dispose(c)
        # 处理完结毁掉子进程
        os._exit(0)
    else:
        # 父进程等候其他用户衔接不需求和子进程通讯
        c.close()

2)根据threading的多线程网络并发

完结过程

  1. 树立监听套接字;
  2. 等候接纳客户端恳求;
  3. 客户端链接树立新的线程处理客户端恳求;
  4. 主线程继续等候其余客户端链接;
  5. 若是客户端退出,则对应分支线程退出。

示例如下:

from socket import *
from threading import Thread
import sys
# 创立监听套接字
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST,PORT)
# 处理客户端恳求
def handle(c):
  while True:
    data = c.recv(1024)
    if not data:
      break
    print(data.decode())
    c.send(b'OK')
  c.close()
s = socket()  # tcp套接字
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(3)
print("Listen the port %d..."%PORT)
# 循环等候客户端衔接
while True:
  try:
    c,addr = s.accept()
  except KeyboardInterrupt:
    sys.exit("服务器退出")
  except Exception as e:
    print(e)
    continue
  # 创立线程处理客户端恳求
  t = Thread(target=handle, args=(c,))
  t.setDaemon(True)   # 父进程完毕则一切进程中止
  t.start()

文章篇幅有点长,上面的示例用到了socket,等到讲到socket再具体解说,socket就放到下篇文章介绍了,请小伙伴耐性等候~


Python 高档编程之并发与多线程就先介绍到这儿,有疑问的小伙伴欢迎给我留言,后续会继续更新相关技能文章,也可重视我的公众号【大数据与云原生技能共享】深化技能交流~

Python 高级编程之并发与多线程(三)