day10 多进程、多线程(一)

Python 多进程、多线程(一)和IO多路复用

本章内容简介

 1) Python2.7和Python3.5 多继承的区别:

 2) IO多路复用

 3) 多进程,多线程(一)

一. Python2.7和Python3.5 多继承的区别

  1. Python2.7多继承时,A类名如果继承object类,表示是新式类,继承关系和Python3.5是相同的,如下图:

 2. Python2.7多继承时,A类名如果不继承任何类,表示经典类,继承关系如下图:

 3. Python的作用域

  • 作用域 :Python和JavaScript没有块级作用域,Java和c#里有:

比如: Python里可以打印出name;

if 1 == 1:
    name = 12

print(name)

Python的作用域,在函数里表现;

函数中的参数,不被外部调用,比如:

def f1():
    name = 'hailong'
    return name
obj = f1()
print(obj)
print(name)

实例中,函数里定义的name,虽然被执行过了,但是外部还是不能获取到,函数内部的变量name; 这个执行结果,会报错。

4. 为了更好的理解作用域,我们再写个lambd表达式的实例:

li = [lambda :x for x in range(5)]
print(li[3]())

相当于下面的代码:

li = []
for x in range(5):
    def a():
        return x
    li.append(a)

print(li[3]())

代码解析:

  li 是一个列表 ,列表里的元素是五个函数内存地址,如果执行,将被调用;我们给li加了索引值,然后括号,就是执行对应的函数;

函数执行结果:无论索引值输入4以内的几(索引不能超出range值),结果都是4,因为每个函数的返回值都是for循环后x最后赋的值;

二. IO多路复用

IO多路复用是什么?

  • 1.1 多路复用概念:

监听多个描述符(文件描述符(windows下暂不支持)、网络描述符)的状态,如果描述符状态改变 则会被内核修改标志位,进而被进程获取进而进行读写操作

  • 1.2 多路复用两种触发方式:

水平触发(Level Triggered):

将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,但是会增加消耗

边缘触发(Edge Triggered):

只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

  • 1.3 阻塞/非阻塞 模式:

阻塞:

如果阻塞模式则等待数据

非阻塞:

如果非阻塞模式有数据返回数据、无数据直接返回报错

  • 1.4 I/O模型:

同步I/O:

一问一答 等待数据(阻塞模式)或 不管有没有数据都返回(非阻塞模式)

异步I/O:

用户进程问完之后干别的处理结果出来之后告知用户进程

  • 1.5 selec/poll/epoll

相同点和不同点图解

Python IO复用之select

格式:

rList,wList,eList = select.select(argv1,argv2,argv3,timeout)

参数:

argv1 标准输入 监听序列中句柄,连接有变化时,则将变化句柄返回至rList

argv2 如果监听序列中句柄发生变化,有数据变化时, 则将变化句柄返回至wList

argv3 如果监听序列中句柄有错误时 则将错误句柄返回到eList

timeout 设置阻塞时间,如果为2那么将阻塞2s,如果不设置则默认一直阻塞

select 使用之读写分离,实例:

server端代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Liuhailong
# Email : 13552658435@126.com

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1",8009))
sk.listen(5)

inputs = [sk,]
outputs = []
while True:
    rlist,wlist,e = select.select(inputs,outputs,[],1)
    print(len(inputs),len(rlist),len(wlist),len(outputs))
    for r in rlist:
        if r == sk:
            conn,address = r.accept()
            inputs.append(conn)
            conn.sendall(bytes('hello',encoding='utf-8'))
        else:
            print("==============")
            try:
                data = r.recv(1024)
                if not data:
                    raise Exception("断开链接")
                else:
                    outputs.append(r)
            except Exception as e:
                inputs.remove(r)

    for w in wlist:
        w.sendall(bytes("response",encoding="utf-8"))
        outputs.remove(w)

client端代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Liuhailong
# Email : 13552658435@126.com

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 8009,))
data = sk.recv(1024)
print(data)
while True:
    inp = input(">>>")
    sk.sendall(bytes(inp,encoding='utf-8'))
    print(sk.recv(1024))
sk.close()

代码解析:

  这个实例,利用select的参数特性,实现了读写分离,io复用;

执行结果:   开启多个客户端窗口,测试io复用,每个窗口都可以执行自己的输入,显示效果如下:

三. 多线程、多进程

1、多线程简介

       多线程编程技术可以实现代码并行性,优化处理能力,同时功能的更小划分可以使代码的可重用性更好。Python中threading和Queue模块可以用来实现多线程编程。
2、详解
  1)、线程和进程

  进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。进程也可以通过fork和spawn操作来完成其它的任务,不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。

       线程(有时被称为轻量级进程)跟进程有些相似,不同的是所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。线程有开始、顺序执行和结束三部分,它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(也叫睡眠)让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其它的线程共享运行的结果。多个线程共同访问同一片数据不是完全没有危险的,由于数据访问的顺序不一样,有可能导致数据结果的不一致的问题,这叫做竞态条件。而大多数线程库都带有一系列的同步原语,来控制线程的执行和数据的访问。
  2)、使用线程
  (1)全局解释器锁(GIL)
       Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
       对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按以下方式执行:a、设置 GIL;b、切换到一个线程去运行;c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));d、把线程设置为睡眠状态;e、解锁 GIL;d、再次重复以上所有步骤。
        在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
  (2)退出线程
       当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,不可以直接“杀掉”("kill")一个线程。
        不建议使用thread模块,很明显的一个原因是,当主线程退出的时候,所有其它线程没有被清除就退出了。另一个模块threading就能确保所有“重要的”子线程都退出后,进程才会结束。
  (3)Python的线程模块
       Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
       避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
3、多线程threading模块

Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。

import threading
import time

def f1(arg,t=None):
    if t:
        t._delete()
    time.sleep(3)
    print(arg)

# for i in range(10):
#     f1(i)  # 单进程,单线程
for i in range(6):
    t1 = threading.Thread(target=f1,args=(i,))  # 多线程
    t1.start() # 不代表当前线程会被立即执行
    # t1.join()
print('end')

更多方法:

    • start            线程准备就绪,等待CPU调度
    • setName      为线程设置名称
    • getName      获取线程名称
    • setDaemon   设置为后台线程或前台线程(默认)
                         如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                          如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
    • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
    • run              线程被cpu调度后自动执行线程对象的run方法
import threading
import time
 
 
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
 
    def run(self):#定义每个线程要运行的函数
 
        print("running on number:%s" %self.num)
 
        time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

线程锁(Lock、RLock)

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

未使用锁:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time

gl_num = 0

def show(arg):
    global gl_num
    time.sleep(1)
    gl_num +=1
    print(gl_num)

for i in range(10):
    t = threading.Thread(target=show, args=(i,))
    t.start()

print ('main thread stop')

使用线程锁:

#!/usr/bin/env python
#coding:utf-8
   
import threading
import time
   
gl_num = 0
   
lock = threading.RLock()
   
def Func():
    lock.acquire()
    global gl_num
    gl_num +=1
    time.sleep(1)
    print (gl_num)
    lock.release()
       
for i in range(10):
    t = threading.Thread(target=Func)
    t.start()

信号量(Semaphore)

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading,time
 
def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s" %n)
    semaphore.release()
 
if __name__ == '__main__':
 
    num= 0
    semaphore  = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()
原文地址:https://www.cnblogs.com/liuhailong-py-way/p/5661117.html