asyncio模块是python之父写的模块,按说应该是可信赖的,python3.6本子定义为平稳版本。

前边大家学了许多历程间的通讯,多进度并发等等,前些天大家来读书线程,线程和进度是怎么样关系,进度和线程有啥一样而又有哪些两样前几日就来宣布这些答案。

目录

一、开启线程的两种方式
    1.1 直接利用利用threading.Thread()类实例化
    1.2 创建一个类,并继承Thread类
    1.3 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
        1.3.1 谁的开启速度更快?
        1.3.2 看看PID的不同
        1.3.3 练习
        1.3.4 线程的join与setDaemon
        1.3.5 线程相关的其他方法补充

二、 Python GIL
    2.1 什么是全局解释器锁GIL
    2.2 全局解释器锁GIL设计理念与限制

三、 Python多进程与多线程对比
四、锁
    4.1 同步锁
    GIL vs Lock
    4.2 死锁与递归锁
    4.3 信号量Semaphore
    4.4 事件Event
    4.5 定时器timer
    4.6 线程队列queue

五、协程
    5.1 yield实现协程
    5.2 greenlet实现协程
    5.3 gevent实现协程

六、IO多路复用

七、socketserver实现并发
    7.1 ThreadingTCPServer

八、基于UDP的套接字

33、线程与全局解释器锁(GIL),解释器gil

事先大家学了不少经过间的通讯,多进度并发等等,今日咱们来学学线程,线程和经过是如何关联,进度和线程有哪些一样而又有哪些两样明日就来公告这么些答案。

 

一、线程概论

1、何为线程

各类进度有一个地点空间,而且默许就有一个控制线程。尽管把一个经过比喻为一个车间的做事历程那么线程就是车间里的一个一个流水线。

进程只是用来把资源集中到一块(进度只是一个资源单位,或者说资源聚集),而线程才是cpu上的执行单位。

多线程(即多少个控制线程)的定义是,在一个进程中设有两个控制线程,多个控制线程共享该进程的地址空间(资源)

创制进度的费用要远超越线程开进度相当于建一个车间,而开线程相当于建一条流水线。

2、线程和进度的区分

1.Threads share the address space of the process that created it; processes have their own address space.
2.Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
3.Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
4.New threads are easily created; new processes require duplication of the parent process.
5.Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
6.Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

中译:

1、线程共享创建它的进程的地址空间;进程有自己的地址空间。
2、线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。
3、线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。
4、新线程很容易创建;新进程需要复制父进程。
5、线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。
6、对主线程的更改(取消、优先级更改等)可能会影响该进程的其他线程的行为;对父进程的更改不会影响子进程。

3、多线程的亮点

四线程和多进程相同指的是,在一个进程中打开四个线程

1)多线程共享一个进度的地方空间(资源)

2)
线程比进度更轻量级,线程比进度更易于创造可废除,在不可胜计操作系统中,成立一个线程比创造一个进度要快10-100倍,在有雅量线程必要动态和急速修改时,这一表征很有用

3)
若三个线程都是cpu密集型的,那么并无法取得属性上的提升,可是假设存在大量的计量和大度的I/O处理,拥有八个线程允许那个活动竞相臃肿运行,从而会加紧程序执行的进程。

4)
在多cpu系统中,为了最大限度的利用多核,能够开启几个线程,比开进度花费要小的多。(这一条并不适用于python)


 

二、python的产出编程之八线程

1、threading模块介绍

multiprocessing模块的完全效仿了threading模块的接口,二者在行使范围,有很大的相似性,由此不再详细介绍

对multiprocessing模块也不是很熟识的意中人可以复习一下八线程时介绍的小说:

30、进度的基础理论,并发(multiprocessing模块):

合法文档:

2、开启线程的二种艺术(和进程一模一样)

二种办法里大家都有打开进度的章程可以简不难单复习回看

1)方式一:

from threading import Thread
#from  multiprocessing  import  Process
import os
def talk():
    print('%s is running' %os.getpid())

if __name__ == '__main__':
    t=Thread(target=talk)
    # t=Process(target=talk)
    t.start()
    print('主',os.getpid())

2)方式二:

#开启线程
from threading import Thread
import os
class MyThread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('pid:%s name:[%s]is running' %(os.getpid(),self.name))

if __name__ == '__main__':
    t=MyThread('lln')
    t.start()
    print('主T',os.getpid())

#开启进程
from multiprocessing import Process
import os
class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('pid:%s name:[%s]is running' % (os.getpid(), self.name))
if __name__ == '__main__':
    t=MyProcess('lll')
    t.start()
    print('主P',os.getpid())

3、在一个经过下打开多个线程与在一个历程下打开三个子进度的界别

1)相比较速度:(看看hello和主线程/主进度的打印速度)

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')

2)pid的不同:(线程和主进度相同,子进度和主进度分歧)

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('我的pid:',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())

3)数据是不是共享(线程与主进度共享数据,子进度只是将主进度拷贝过去操作的并非同一份数据)

from  threading import Thread
from multiprocessing import Process
def work():
    global n
    n -= 1
n = 100  #主进程数据
if __name__ == '__main__':
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了99,但改的仅仅是它自己的,查看父进程的n仍然为100

    t=Thread(target=work)
    t.start()
    t.join()
    print('主',n) #查看结果为99,因为同一进程内的线程之间共享进程内的数据

 4、练习

1)多个任务,一个收到用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

from threading import Thread
msg = []
msg_fort = []
def Inp():
    while True :
        msg_l = input('>>:')
        if not msg_l : continue
        msg.append(msg_l)
def Fort():
    while True :
        if msg :
            res = msg.pop()
            msg_fort.append(res.upper())
def Save():
    with open('db.txt','a') as f :
        while True :
            if msg_fort :
                f.write('%s\n' %msg_fort.pop())
                f.flush()  #强制将缓冲区中的数据发送出去,不必等到缓冲区满
if __name__ == '__main__':
    p1 = Thread(target=Inp)
    p2 = Thread(target=Fort)
    p3 = Thread(target=Save)
    p1.start()
    p2.start()
    p3.start()

2)将前方随笔中的服务端客户端例子用八线程达成(不了然的可以阅读前几篇小说)

澳门金沙国际 1

from threading import Thread
from socket import *
s=socket(AF_INET,SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)

def action(conn):
    while True:
        data=conn.recv(1024)
        print(data)
        conn.send(data.upper())

if __name__ == '__main__':
    while True:
        conn,addr=s.accept()
        p=Thread(target=action,args=(conn,))
        p.start()

服务端
澳门金沙国际 2

if __name__ == '__main__':
    while True:
        conn,addr=s.accept()
        p=Thread(target=action,args=(conn,))
        p.start()

from socket import *
s=socket(AF_INET,SOCK_STREAM)
s.connect(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if not msg:continue

    s.send(msg.encode('utf-8'))
    data=s.recv(1024)
    print(data)

客户端

5、threading模块别的方法

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

澳门金沙国际 3

from threading import Thread,currentThread,activeCount
import os,time,threading
def talk():
    print('%s is running' %currentThread().getName())

if __name__ == '__main__':
    # t=Thread(target=talk,name='egon')
    t=Thread(target=talk)
    t.start()
    # print(t.name)
    # print(t.getName())
    # print(t.is_alive())
    # print(currentThread().getName())
    print(threading.enumerate())
    time.sleep(3)
    print(t.is_alive())
    print('主',activeCount())

多路IO复用补充asyncio,IO多路复用。测试

主线程等其他线程

from threading import Thread,currentThread,activeCount
import os,time,threading
def talk():
    time.sleep(2)
    print('%s is running' %currentThread().getName())

if __name__ == '__main__':
    t=Thread(target=talk)
    t.start()
    t.join()
    print('主')

6、守护线程

1)守护线程和医护进度的区分

对主进程来说,运行已毕指的是主进度代码运行完成

对主线程来说,运行达成指的是主线程所在的经过内享有非守护线程统统运行落成,主线程才算运行已毕

2)详细表明

主进度在其代码截至后就已经算运行达成了(守护进度在那时就被回收),然后主过程会平昔等非守护的子过程都运作完毕后回收子进程的资源(否则会发出僵尸进程),才会截至

主线程在其余非守护线程运行落成后才算运行落成(守护线程在此刻就被回收)。因为主线程的了断表示进度的了断,进程全部的资源都将被回收,而经过必须确保非守护线程都运作已毕后才能终止。

澳门金沙国际 4

from threading import Thread,currentThread
import os,time
def talk1():
    time.sleep(10)
    print('%s is running' %currentThread().getName())
def talk2():
    time.sleep(2)
    print('%s is running' %currentThread().getName())
def talk3():
    time.sleep(4)
    print('%s is running' %currentThread().getName())
if __name__ == '__main__':
    t1 = Thread(target=talk1)
    t2 = Thread(target=talk2)
    t3 = Thread(target=talk2)
    t1.daemon = True
    t2.daemon = True
    t1.start()
    t2.start()
    t3.start()
    print('主线程',os.getpid())

守护线程
澳门金沙国际 5

from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


t1=Thread(target=foo)
t2=Thread(target=bar)

t1.daemon=True
t1.start()
t2.start()
print("main-------")

迷惑人的例子


 

三、Python GIL(Global Interpreter Lock)

1、定义:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once.

在CPython中,全局解释器锁是一个互斥锁,或GIL,它可以防止多个本地线程执行Python字节码。

This lock is necessary mainly because CPython’s memory management is not thread-safe.

这个锁是必需的,主要是因为CPython的内存管理不是线程安全的。

(However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

然而,由于GIL存在,其他的特性已经发展到依赖于它的保证。

敲定:在Cpython解释器中,同一个进程下打开的四线程,同一时刻只好有一个线程执行,无法运用多核优势

注意:

首先须求驾驭的一点是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

对协调塞尔维亚共和国(Republic of Serbia)语水平有信心的可以看一下: (这篇作品透彻的解析了GIL对python八线程的震慑)

2、GIL介绍

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都同一,都是将并发运行变成串行,以此来控制同一时间内共享数据只好被一个职务所修改,进而有限辅助数据安全。

可以肯定的一些是:珍视差其余数据的平安,就活该加不相同的锁。

要想领悟GIL,首先确定一点:每回执行python程序,都会时有产生一个独自的进度。例如python
test.py,python aaa.py,python bbb.py会暴发3个例外的python进度

'''
#验证python test.py只会产生一个进程
#test.py内容
import os,time
print(os.getpid())
time.sleep(1000)
'''
python3 test.py 
#在windows下
tasklist |findstr python
#在linux下
ps aux |grep python

验证python test.py只会产生一个进程

在一个python的长布里斯班,不仅有test.py的主线程或者由该主线程开启的别样线程,还有解释器开启的废料回收等解释器级其他线程,不言而喻,所有线程都运作在那些进程内,毫无疑问:

#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
#2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

综上:

借使三个线程的target=work,那么执行流程是八个线程先走访到解释器的代码,即得到实践权限,然后将target的代码交给解释器的代码去执行

GIL保养的是解说器级的数码,敬爱用户自己的数额则需求协调加锁处理

澳门金沙国际 6

from threading import Thread,Lock
import time
n=100
def work():
    # mutex.acquire()
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1
    # mutex.release()

if __name__ == '__main__':
    mutex=Lock()
    t_l=[]
    s=time.time()
    for i in range(100):
        t=Thread(target=work)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
    print('%s:%s' %(time.time()-s,n))

有限支撑自己的数据如故要求团结加锁

3、GIL与多线程

有了GIL的存在,同一时刻同一进度中唯有一个线程被执行

听见那里,你是还是不是会有疑难:进度可以使用多核,但是付出大,而python的十六线程成本小,但却力不从心运用多核优势,也就是说python没用了

要缓解那些难题,大家须要在多少个点上落成一致:

#1. cpu到底是用来做计算的,还是用来做I/O的?
#2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
#3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处 

结论:

对计量的话,cpu愈来愈多越好,但是对于I/O来说,再多的cpu也没用

理所当然对运作一个程序来说,随着cpu的增多执行成效肯定会有所升高(不管进步幅度多大,总会有所升高),那是因为一个程序基本上不会是纯计算如故纯I/O,所以我们只可以相对的去看一个先后到底是测算密集型照旧I/O密集型,从而进一步分析python的多线程到底有无用武之地

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程
#单核情况下,分析结果: 
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
#多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
#结论:
现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

4、质量测试

澳门金沙国际 7

from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        p=Process(target=work) #耗时5s多
        p=Thread(target=work) #耗时18s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

总结密集型:多进度效用高
澳门金沙国际 8

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('===>')

if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
        p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

I/O密集型:多线程功能高

总结:

二十四线程用于IO密集型,如socket,爬虫,web

多进度用于总结密集型,如金融分析

以前大家学了许多历程间的通讯,多进度并发等等,明日大家来学习线程,线程和进度是哪些关…

说明书:

 

一、开启线程的二种方式

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

同开启进度的三种办法相同:

约莫定义:该模块提供了应用协程编写单线程并发代码,通过套接字和其它资源复用I​​
/ O访问,运行互联网客户端和服务器以及任何连锁原语的底子结构。

本篇导航:

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('主线程')

粗略利用(基于wsgi的报警器)

  • 线程概论
  • python的出现编程之多线程
  • Python GIL(Global Interpreter
    Lock)

1.2 创设一个类,并持续Thread类

from threading import Thread
import time
calss Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name
    def run(self):
        time.sleep(2)
        print("%s say hello" %self.name)

if __name__ == "__main__":
    t = Sayhi("egon")
    t.start()
    print("主线程")
 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # @Time    : 2018/8/21 10:40
 4 # @Author  : WangYue
 5 # @Site    : 
 6 # @File    : alertor_uwsgi.py
 7 # @Software: PyCharm
 8 
 9 #加入环境变量避免程序报错
10 import sys,os
11 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
12 
13 #引入wsgi模型,这里主要是弄一个简单的http模型来说明asyncio的简单使用
14 from wsgiref.simple_server import make_server
15 
16 #引入asyncio
17 import asyncio
18 
19 #引入其他可能的依赖
20 from threading import Thread
21 import json
22 
23 #引入本程序其他的内容,与asyncio无关是程序业务的其他部分
24 from conf.alertor_conf import ALERTOR_CONFIG
25 from model.alert_method_class import SendMail
26 
27 #定义一个运行asyncio loop 的线程,去单独运行它
28 def start_loop(loop):
29     asyncio.set_event_loop(loop)
30     loop.run_forever()
31 new_loop = asyncio.new_event_loop() #这个new_loop将会用于运行异步程序
32 t = Thread(target=start_loop, args=(new_loop,))
33 t.start() #利用多线程,额外起一个线程运行asyncio loop然后它在自身使用协程之类处异步处理业务
34 
35 
36 #这是wsgi主业务程序,application是wsgi的入口程序,wsgi就规定了这么个函数名,这样写wsgi就认可它是入口了。传参也是wsgi的规定,第一个是环境,第二个是响应
37 def application(env,start_res):
38     res_headers=[('Content-type', 'text/plain')]
39     if env["REQUEST_METHOD"]=='POST':
40         # the environment variable CONTENT_LENGTH may be empty or missing
41         try:
42             if env['PATH_INFO'] != "/send_alert":
43                 status = "404 func is not in use"
44                 start_res(status, res_headers)
45                 return [b"func is not in use"]
46 
47             request_body_size = int(env.get('CONTENT_LENGTH', 0))
48             status = "200 OK"
49             request_body = env['wsgi.input'].read(request_body_size)
50             print("post_info -->", request_body.decode())
51             r_body=json.loads(request_body.decode())
52             #就这里一行代码,new_loop.call_soon_threadsafe(),告诉asyncio去运行第一个参数的函数名,后边的参数是被运行函数的传参,有多少就传参多少个,这是异步的,非阻塞的。
53             new_loop.call_soon_threadsafe(SendMail.sendEmail,"Alertor Alarm level:"+r_body["level"]+"  server: "+r_body["server"],r_body)
54             start_res(status,res_headers)
55             return [b"ok send alert......."]
56         except (ValueError):
57             status = "404 json data not found key"
58             request_body_size = 0
59             start_res(status, res_headers)
60             return [b"get post info faild"]
61 
62     else:
63         status = "403 method error"
64         start_res(status,res_headers)
65         return [b'method error']
66 
67 # 1、只接受POST请求。数据为json格式,json中标记的,包括但不限于,包括的信息将会入库,其他信息,在告警时会一并发出
68 # {
69 #     "level":"high", #告警级别,"high","medium","info",这个可以在配置文件中配置,配置信息是个列表,有序的从左至右为["high","medium","info"],对应后续告警逻辑及post中json的本字段。
70 #     "@timestamp":"",#告警时间
71 #     "server":"",#告警源,可以是ip,主机名,服务名等可标识的
72 #     "message":""#具体的告警信息
73 # }
74 # 2、根据json中的level自动选择告警途径,选择方式,在配置文件中的alert_method字典信息
75 # 3、将告警内容,存储数据库,便于日后查询
76 # 4、后续提供查询统计告警信息的方法
77 
78 
79 if __name__=="__main__":
80     wsgi_server=make_server(ALERTOR_CONFIG['bind_ip'],ALERTOR_CONFIG['port'],application)
81 
82     wsgi_server.serve_forever()

 

1.3 在一个经过下打开五个线程与在一个历程下打开多少个子进度的区分

眼下此地先这么用,那些模型的属性是基于uwsgi运行,2进度,每个进程内4个线程,基准质量是15000呼吁总量,5000客户端

一、线程概论

1.3.1 哪个人的敞开速度更快?

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello
    主线程/主进程
    '''

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程
    hello
    '''

结论:由于创建子进度是将主进程完全拷贝一份,而线程不须求,所以线程的始建速度更快。

 ab -n 15000-c 5000 -p test_alert.txt -T
application/x-www-form-urlencoded “”

1、何为线程

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。

效益还聚集吧。

每个进度有一个地址空间,而且默许就有一个决定线程。若是把一个进度比喻为一个车间的办事经过那么线程就是车间里的一个一个流程。

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"))

练习二:有三个义务,一个吸收用户输入,一个将用户输入的始末格式化成大写,一个将格式化后的结果存入文件。

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()

进度只是用来把资源集中到一块(进度只是一个资源单位,或者说资源聚集),而线程才是cpu上的推行单位。

1.3.4 线程的join与setDaemon

与经过的措施都是接近的,其实multiprocessing模块是模拟threading模块的接口;

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.setDaemon(True) #设置为守护线程,主线程结束,子线程也跟着线束。
    t.start()
    t.join()  #主线程等待子线程运行结束
    print('主线程')
    print(t.is_alive())

三十六线程(即多少个控制线程)的定义是,在一个进度中存在多少个控制线程,三个控制线程共享该进度的地址空间(资源)

1.3.5 线程相关的别的办法补充

Thread实例对象的不二法门:

  • isAlive():再次来到纯种是或不是是活跃的;
  • getName():再次回到线程名;
  • setName():设置线程名。

threading模块提供的有些措施:

  • threading.currentThread():重返当前的线程变量
  • threading.enumerate():再次来到一个暗含正在运作的线程的列表。正在运作指线程启动后、甘休前,不包涵启动前和为止后。
  • threading.activeCount():重临正在运作的线程数量,与len(threading.enumerate())有同一结果。

from threading import Thread
import threading
import os

def work():
    import time
    time.sleep(3)
    print(threading.current_thread().getName())


if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()

    print(threading.current_thread().getName()) #获取当前线程名
    print(threading.current_thread()) #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程,返回的是活跃的线程列表
    print(threading.active_count())  #活跃的线程个数
    print('主线程/主进程')

    '''
    打印结果:
    MainThread
    <_MainThread(MainThread, started 140735268892672)>
    [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]
    2
    主线程/主进程
    Thread-1
    '''

开创进度的开发要远超出线程开进度相当于建一个车间,而开线程相当于建一条流水线。

二、 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

2、线程和经过的区分

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
的字节码被周转,所以不会做线程切换)。

1.Threads share the address space of the process that created it; processes have their own address space.
2.Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
3.Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
4.New threads are easily created; new processes require duplication of the parent process.
5.Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
6.Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

2.2 全局解释器锁GIL设计理念与范围

GIL的统筹简化了CPython的落到实处,使得对象模型,包罗主要的内建类型如字典,都是带有可以并发访问的。锁住全局解释器使得比较易于的贯彻对二十四线程的支撑,但也损失了多处理器主机的并行总计能力。
但是,不论标准的,仍旧第三方的恢弘模块,都被设计成在拓展密集计算任务是,释放GIL。
还有,就是在做I/O操作时,GIL总是会被放飞。对具有面向I/O
的(会调用内建的操作系统C 代码的)程序来说,GIL 会在那一个I/O
调用从前被保释,以允许其余的线程在这几个线程等待I/O
的时候运行。即使是纯统计的先后,没有 I/O 操作,解释器会每隔 100
次操作就自由那把锁,让其他线程有机遇执行(那么些次数可以透过
sys.setcheckinterval 来调整)若是某线程并未选拔过多I/O
操作,它会在温馨的时刻片内一向占据处理器(和GIL)。也就是说,I/O
密集型的Python 程序比总结密集型的主次更能丰富利用三十二线程环境的益处。

上边是Python 2.7.9手册中对GIL的简要介绍:
The mechanism used by the CPython interpreter to assure that only one
thread executes Python bytecode at a time. This simplifies the CPython
implementation by making the object model (including critical built-in
types such as dict) implicitly safe against concurrent access. Locking
the entire interpreter makes it easier for the interpreter to be
multi-threaded, at the expense of much of the parallelism afforded by
multi-processor machines.
However, some extension modules, either standard or third-party, are
designed so as to release the GIL when doing computationally-intensive
tasks such as compression or hashing. Also, the GIL is always released
when doing I/O.
Past efforts to create a “free-threaded” interpreter (one which locks
shared data at a much finer granularity) have not been successful
because performance suffered in the common single-processor case. It is
believed that overcoming this performance issue would make the
implementation much more complicated and therefore costlier to maintain.

从上文中能够看出,针对GIL的题材做的多如牛毛革新,如采取更细粒度的锁机制,在单处理器环境下反而导致了质量的低沉。普遍认为,克制那几个特性难点会导致CPython完结更为错综复杂,由此维护资金更是昂扬。

中译:

三、 Python多进度与三二十四线程对比

有了GIL的留存,同一时刻同一进度中只有一个线程被实践?那里恐怕人有一个疑问:多进度可以使用多核,然则付出大,而Python八线程开销小,但却不知所可运用多核的优势?要解决这么些题材,大家必要在以下几点上达到共识:

  • CPU是用来测算的!
  • 多核CPU,意味着可以有多个核并行落成统计,所以多核升级的是计算质量;
  • 每个CPU一旦蒙受I/O阻塞,依然须求拭目以待,所以多核查I/O操作没什么用处。

当然,对于一个顺序来说,不会是纯总结仍然纯I/O,大家只好相对的去看一个主次到底是测算密集型,依旧I/O密集型。从而尤其分析Python的八线程有无用武之地。

分析:

大家有多个任务急需处理,处理访求肯定是要有出现的功力,解决方案得以是:

  • 方案一:开启多个进度;
  • 方案二:一个进度下,开启三个经过。

单核情状下,分析结果:

  • 若是多个职务是持筹握算密集型,没有多核来并行计算,方案一徒增了创办进度的付出,方案二胜;
  • 比方多个职责是I/O密集型,方案一开立进度的支付大,且经过的切换速度远不如线程,方案二胜。

多核情状下,分析结果:

  • 借使多个职责是密集型,多核意味着并行
    总计,在python中一个进度中平等时刻唯有一个线程执行用不上多核,方案一胜;
  • 假如八个任务是I/O密集型,再多的核 也解决不了I/O难题,方案二胜。

结论:前几日的微机基本上都是多核,python对于总结密集型的天职开多线程的频率并不可能牵动多大质量上的晋级,甚至
不如串行(没有大气切换),然则,对于I/O密集型的天职效用仍然有显明升级的。

代码落成比较

总结密集型:

#计算密集型
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
    res=0
    for i in range(1000000):
        res+=i

if __name__ == '__main__':
    t_l=[]
    start_time=time.time()
    for i in range(100):
        # t=Thread(target=work) #我的机器4核cpu,多线程大概15秒
        t=Process(target=work) #我的机器4核cpu,多进程大概10秒
        t_l.append(t)
        t.start()

    for i in t_l:
        i.join()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    print('主线程')

I/O密集型:

#I/O密集型
from threading import Thread
from multiprocessing import Process
import time
import os
def work():
    time.sleep(2) #模拟I/O操作,可以打开一个文件来测试I/O,与sleep是一个效果
    print(os.getpid())

if __name__ == '__main__':
    t_l=[]
    start_time=time.time()
    for i in range(500):
        # t=Thread(target=work) #run time is 2.195
        t=Process(target=work) #耗时大概为37秒,创建进程的开销远高于线程,而且对于I/O密集型,多cpu根本不管用
        t_l.append(t)
        t.start()

    for t in t_l:
        t.join()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))

总结:
采纳场景:
二十四线程用于I/O密集型,如socket、爬虫、web
多进程用于统计密集型,如金融分析

1、线程共享创建它的进程的地址空间;进程有自己的地址空间。
2、线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。
3、线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。
4、新线程很容易创建;新进程需要复制父进程。
5、线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。
6、对主线程的更改(取消、优先级更改等)可能会影响该进程的其他线程的行为;对父进程的更改不会影响子进程。

四、锁

3、十六线程的独到之处

4.1 同步锁

必要:对一个全局变量,开启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阻塞后高速切换来下一个线程执行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()

三八线程和多进度相同指的是,在一个进度中打开几个线程

GIL vs Lock

机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 

首先我们必要落成共识:锁的目标是为了掩护共享的多寡,同一时间只好有一个线程来修改共享的数据

接下来,大家可以得出结论:珍惜差别的数额就活该加不一样的锁。

最终,难点就很爽朗了,GIL
与Lock是两把锁,爱护的数额分化,前者是解释器级其他(当然维护的就是解释器级其余多寡,比如垃圾回收的多寡),后者是保安用户自己付出的应用程序的数量,很明朗GIL不担负那件事,只好用户自定义加锁处理,即Lock

详细的:

因为Python解释器帮您活动定期进行内存回收,你可以知晓为python解释器里有一个单身的线程,每过一段时间它起wake
up做一遍全局轮询看看哪些内存数据是可以被清空的,此时你协调的顺序
里的线程和
py解释器自己的线程是并发运行的,要是你的线程删除了一个变量,py解释器的废品回收线程在清空那些变量的进程中的clearing时刻,可能一个任何线程正好又再一次给这些还没来及得清空的内存空间赋值了,结果就有可能新赋值的数码被剔除了,为了化解类似的标题,python解释器简单凶横的加了锁,即当一个线程运行时,另别人都不可以动,那样就一蹴而就了上述的标题,
那可以说是Python早期版本的遗留难题。

1)三三十二线程共享一个历程的地址空间(资源)

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和一个counter变量,counter记录了acquire(得到锁)的次数,从而使得资源可以被一再require。直到一个线程所有的acquire都被release(释放)后,其余的线程才能博取资源。上边的例子如果选取RLock代替Lock,就不会爆发死锁的情景了。

mutexA=mutexB=threading.RLock()
#一个线程得到锁,counter加1,该线程内又赶上加锁的场地,则counter继续加1,那之间所有其余线程都只可以等待,等待该线程释放具有锁,即counter递减到0截至。

2)
线程比进度更轻量级,线程比进度更便于创设可收回,在不少操作系统中,创设一个线程比创立一个历程要快10-100倍,在有多量线程必要动态和飞速修改时,这一特征很有用

4.3 信号量Semaphore

同进度的信号量一样。
用一个粗鄙的例子来说,锁相当于独立卫生间,唯有一个坑,同一时刻只可以有一个人得到锁,进去使用;而信号量相当于公私更衣室,例如有5个坑,同一时刻可以有5个人拿走锁,并动用。

Semaphore管制一个停放的计数器,每当调用acquire()时,内置计数器-1;调用release()时,内置计数器+1;计数器无法小于0,当计数器为0时,acquire()将阻塞线程,直到其他线程调用release()

实例:
同时唯有5个线程可以博得Semaphore,即可以界定最大连接数为5:

import threading
import time

sem = threading.Semaphore(5)
def func():
    if sem.acquire():   #也可以用with进行上下文管理
        print(threading.current_thread().getName()+"get semaphore")
        time.sleep(2)
        sem.release()

for i in range(20):
    t1 = threading.Thread(target=func)
    t1.start()

利用with拓展上下文管理:

import threading
import time

sem = threading.Semaphore(5)

def func():
    with sem:   
        print(threading.current_thread().getName()+"get semaphore")
        time.sleep(2)

for i in range(20):
    t1 = threading.Thread(target=func)
    t1.start()

注:信号量与进度池是全然不一致一的定义,进度池Pool(4)最大不得不发出4个经过,而且从头到尾都只是那4个进度,不会发生新的,而信号量是发出一堆线程/进度。

3)
若七个线程都是cpu密集型的,那么并不能得到属性上的增强,不过一旦存在大气的盘算和大度的I/O处理,拥有多少个线程允许那几个活动竞相臃肿运行,从而会加紧程序执行的进程。

4.4 事件Event

同进度的一致

线程的一个第一特性是各种线程都是独立运行且情况不行预测。如若程序中的其他线程通过判断某个线程的景况来确定自己下一步的操作,那时线程同步难点就会变得非凡讨厌,为了缓解这几个难题大家选用threading库中的Event对象。

Event对象涵盖一个可由线程设置的信号标志,它同意线程等待某些事件的爆发。在始发情状下,伊夫nt对象中的信号标志被设置为假。若是有线程等待一个伊芙nt对象,而以此伊芙nt对象的申明为假,那么那几个线程将会被
一贯不通直至该
标志为真。一个线程如若将一个伊夫nt对象的信号标志设置为真,它将唤起所有等待那么些伊芙nt对象的线程。要是一个线程等待一个业已被
设置 为实在伊夫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方式还足以承受一个超时参数,默许景况下,若是事件直接没有发生,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服务启动的同时,看到工作线程太史在等待的情形。应用:连接池。

4)
在多cpu系统中,为了最大限度的施用多核,可以敞开七个线程,比开进度费用要小的多。(这一条并不适用于python)

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

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')
'''

 

五、协程

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

内需强调的是:

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

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

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

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

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

要落到实处协程,关键在于用户程序自己控制程序切换,切换以前务必由用户程序自己保留协程上一回调用时的情形,如此,每便重复调用时,能够从上次的义务继续执行

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

二、python的产出编程之十二线程

澳门金沙国际 ,5.1 yield落成协程

俺们前边已经学习过一种在单线程下可以保存程序运行状态的主意,即yield,我们来差不多复习一下:

  • yiled可以保存景况,yield的动静保存与操作系统的保留线程状态很像,不过yield是代码级别决定的,更轻量级
  • send可以把一个函数的结果传给其余一个函数,以此已毕单线程内程序之间的切换

#不用yield:每次函数调用,都需要重复开辟内存空间,即重复创建名称空间,因而开销很大
import time
def consumer(item):
    # print('拿到包子%s' %item)
    x=11111111111
    x1=12111111111
    x3=13111111111
    x4=14111111111
    y=22222222222
    z=33333333333

    pass
def producer(target,seq):
    for item in seq:
        target(item) #每次调用函数,会临时产生名称空间,调用结束则释放,循环100000000次,则重复这么多次的创建和释放,开销非常大

start_time=time.time()
producer(consumer,range(100000000))
stop_time=time.time()
print('run time is:%s' %(stop_time-start_time)) #30.132838010787964


#使用yield:无需重复开辟内存空间,即重复创建名称空间,因而开销小
import time
def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

init
def consumer():
    x=11111111111
    x1=12111111111
    x3=13111111111
    x4=14111111111
    y=22222222222
    z=33333333333
    while True:
        item=yield
        # print('拿到包子%s' %item)
        pass
def producer(target,seq):
    for item in seq:
        target.send(item) #无需重新创建名称空间,从上一次暂停的位置继续,相比上例,开销小

start_time=time.time()
producer(consumer(),range(100000000))
stop_time=time.time()
print('run time is:%s' %(stop_time-start_time)) #21.882073879241943

缺点:
协程的精神是单线程下,不可以运用多核,可以是一个先后开启多个经过,每个进度内打开多个线程,每个线程内打开协程。
协程指的是单个线程,因此一旦协程现身堵塞,将会阻塞整个线程。

协程的概念(满意1,2,3就足以称作协程):

  1. 不可能不在唯有一个单线程里已毕产出
  2. 修改共享数据不需加锁
  3. 用户程序里自己保留多少个控制流的前后文栈
  4. 外加:一个协程蒙受IO操作自动切换来任何协程(怎么样落实检测IO,yield、greenlet都无法兑现,就用到了gevent模块(select机制))

注意:yield切换在向来不io的景观下或者没有再度开发内存空间的操作,对功用没有什么进步,甚至更慢,为此,能够用greenlet来为大家演示那种切换。

1、threading模块介绍

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
'''

可以在首先次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模块了。

multiprocessing模块的完全模仿了threading模块的接口,二者在应用范围,有很大的相似性,由此不再详细介绍

5.3 gevent完结协程

gevent是一个第三方库,可以轻松通过gevent已毕产出同步或异步编程,在gevent中用到的严重性是Greenlet,它是以C增添模块格局接入Python的轻量级协程。greenlet一体运转在主程操作系统进度的其中,但它们被合作式地调试。遭受I/O阻塞时会自动切换任务。

注意:gevent有投机的I/O阻塞,如:gevent.sleep()和gevent.socket();但是gevent不可能一向识别除自身之外的I/O阻塞,如:time.sleep(2),socket等,要想识别那几个I/O阻塞,必须打一个补丁: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)

对multiprocessing模块也不是很了解的情人可以复习一下八线程时介绍的随笔:

六、IO多路复用

30、进程的基础理论,并发(multiprocessing模块):

经过IO多路复用达成同时监听多少个端口的服务端

示例一:

# 示例一:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author : Cai Guangyin

from socket import socket
import select

sock_1 = socket()
sock_1.bind(("127.0.0.1",60000))
sock_1.listen(5)

sock_2 = socket()
sock_2.bind(("127.0.0.1",60001))
sock_2.listen(5)

inputs = [sock_1,sock_2]

while True:
    # IO多路复用
    # -- select方法,内部进行循环操作,哪个socket对象有变化(连接),就赋值给r;监听socket文件句柄有个数限制(1024个)
    # -- poll方法,也是内部进行循环操作,没有监听个数限制
    # -- epoll方法,通过异步回调,哪个socket文件句柄有变化,就会自动告诉epoll,它有变化,然后将它赋值给r;
    # windows下没有epoll方法,只有Unix下有,windows下只有select方法
    r,w,e=select.select(inputs,[],[],0.2)  #0.2是超时时间
        #当有人连接sock_1时,返回的r,就是[sock_1,];是个列表
        #当有人连接sock_2时,返回的r,就是[sock_2,];是个列表
        #当有多人同时连接sock_1和sock_2时,返回的r,就是[sock_1,sock_2,];是个列表
        #0.2是超时时间,如果这段时间内没有连接进来,那么r就等于一个空列表;
    for obj in r:
        if obj in [sock_1,sock_2]:

            conn, addr = obj.accept()
            inputs.append(conn)
            print("新连接来了:",obj)

        else:
            print("有连接用户发送消息来了:",obj)
            data = obj.recv(1024)
            if not data:break
            obj.sendall(data)

客户端:

# -*- coding:utf-8 -*-
#!/usr/bin/python
# Author : Cai Guangyin

from socket import *

tcpsock = socket(AF_INET,SOCK_STREAM)   #创建一个tcp套接字
tcpsock.connect(("127.0.0.1",60001))     #根据地址连接服务器

while True:   #客户端通信循环
    msg = input(">>: ").strip()   #输入消息
    if not msg:continue           #判断输入是否为空
        #如果客户端发空,会卡住,加此判断,限制用户不能发空
    if msg == 'exit':break       #退出
    tcpsock.send(msg.encode("utf-8"))   #socket只能发送二进制数据
    data = tcpsock.recv(1024)    #接收消息
    print(data.decode("utf-8"))

tcpsock.close()

以上服务端运行时,假使有客户端断开连接则会抛出如下很是:

澳门金沙国际 9

异常

合法文档:(匈牙利(Magyarország)语好的可以品味挑战)

革新版如下

募集相当并将接收数据和发送数据分开处理
示例二:

# 示例二
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author : Cai Guangyin

from socket import *
import select

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

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


inputs = [sk1,sk2]
w_inputs = []

while True:
    r,w,e = select.select(inputs,w_inputs,inputs,0.1)
    for obj in r:
        if obj in [sk1,sk2]:
            print("新连接:",obj.getsockname())
            conn,addr = obj.accept()
            inputs.append(conn)

        else:
            try:
                # 如果客户端断开连接,将获取异常,并将收取数据data置为空
                data = obj.recv(1024).decode('utf-8')
                print(data)
            except Exception as e:
                data = ""

            if data:
                # 如果obj能正常接收数据,则认为它是一个可写的对象,然后将它加入w_inputs列表
                w_inputs.append(obj)
            else:
                # 如果数据data为空,则从inputs列表中移除此连接对象obj
                print("空消息")
                obj.close()
                inputs.remove(obj)


        print("分割线".center(60,"-"))

    # 遍历可写的对象列表,
    for obj in w:
        obj.send(b'ok')
        # 发送数据后删除w_inputs中的此obj对象,否则客户端断开连接时,会抛出”ConnectionResetError“异常
        w_inputs.remove(obj)

2、开启线程的两种格局(和进程一模一样)

七、socketserver完毕产出

基于TCP的套接字,关键就是七个巡回,一个总是循环,一个通讯循环。

SocketServer内部运用 IO多路复用 以及 “二十四线程” 和 “多进程”
,从而完毕产出处理七个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创办一个“线程”或者“进度”
专门负责处理当下客户端的具备请求。

socketserver模块中的类分为两大类:server类(解决链接问题)和request类(解决通讯难题)

server类:

澳门金沙国际 10

server类

request类:

澳门金沙国际 11

request类

线程server类的存续关系:

澳门金沙国际 12

线程server类的继续关系

经过server类的接续关系:

澳门金沙国际 13

进度server类的持续关系

request类的后续关系:

澳门金沙国际 14

request类的存续关系

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()

找寻属性的逐条:ThreadingTCPServer –> ThreadingMixIn –>
TCPServer->BaseServer

  1. 实例化获得ftpserver,先找类ThreadingTCPServer__init__,在TCPServer中找到,进而实施server_bind,server_active
  2. ftpserver下的serve_forever,在BaseServer中找到,进而实施self._handle_request_noblock(),该措施同样是在BaseServer
  3. 执行self._handle_request_noblock()随后实施request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后实施self.process_request(request, client_address)
  4. ThreadingMixIn中找到process_request,开启三十二线程应对出现,进而实施process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四片段形成了链接循环,本有的开首进入拍卖通信部分,在BaseServer中找到finish_request,触发大家团结定义的类的实例化,去找__init__艺术,而我们和好定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找….

源码分析总计:
基于tcp的socketserver我们自己定义的类中的

  • self.server 即套接字对象
  • self.request 即一个链接
  • self.client_address 即客户端地址

基于udp的socketserver大家团结定义的类中的

  • self.request是一个元组(第二个要素是客户端发来的数量,第二有的是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  • self.client_address即客户端地址。

三种艺术里大家都有打开进程的形式得以大致复习回看

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()

1)方式一:

七、基于UDP的套接字

  • recvfrom(buffersize[, flags])收到新闻,buffersize是五遍接到多少个字节的数目。
  • sendto(data[, flags], address)
    发送音信,data是要发送的二进制数据,address是要发送的地址,元组情势,包括IP和端口

服务端:

from socket import *
s=socket(AF_INET,SOCK_DGRAM)  #创建一个基于UDP的服务端套接字,注意使用SOCK_DGRAM类型
s.bind(('127.0.0.1',8080))  #绑定地址和端口,元组形式

while True:    #通信循环
    client_msg,client_addr=s.recvfrom(1024) #接收消息
    print(client_msg)
    s.sendto(client_msg.upper(),client_addr) #发送消息

客户端:

from socket import *
c=socket(AF_INET,SOCK_DGRAM)   #创建客户端套接字

while True:
    msg=input('>>: ').strip()
    c.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) #发送消息
    server_msg,server_addr=c.recvfrom(1024) #接收消息
    print('from server:%s msg:%s' %(server_addr,server_msg))

依傍即时聊天
鉴于UDP无连接,所以可以而且五个客户端去跟服务端通讯

服务端:

from socket import *

server_address = ("127.0.0.1",60000)
udp_server_sock = socket(AF_INET,SOCK_DGRAM)
udp_server_sock.bind(server_address)

while True:
    qq_msg,addr = udp_server_sock.recvfrom(1024)
    print("来自[%s:%s]的一条消息:\033[32m%s\033[0m"%(addr[0],addr[1],qq_msg.decode("utf-8")))
    back_msg = input("回复消息:").strip()
    udp_server_sock.sendto(back_msg.encode("utf-8"),addr)

udp_server_sock.close()

客户端:

from socket import *

BUFSIZE = 1024
udp_client_sock = socket(AF_INET,SOCK_DGRAM)
qq_name_dic = {
    "alex":("127.0.0.1",60000),
    "egon":("127.0.0.1",60000),
    "seven":("127.0.0.1",60000),
    "yuan":("127.0.0.1",60000),
}

while True:
    qq_name = input("请选择聊天对象:").strip()
    while True:
        msg = input("请输入消息,回车发送:").strip()
        if msg == "quit":break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        print(msg,qq_name_dic[qq_name])
        udp_client_sock.sendto(msg.encode("utf-8"),qq_name_dic[qq_name])

        back_msg,addr = udp_client_sock.recvfrom(BUFSIZE)
        print("来自[%s:%s]的一条消息:\033[32m%s\033[0m" %(addr[0],addr[1],back_msg.decode("utf-8")))
udp_client_sock.close()

注意:
1.你独自运行方面的udp的客户端,你发觉并不会报错,相反tcp却会报错,因为udp协议只承担把包发出去,对方收不收,我常有不管,而tcp是根据链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递新闻,任何一方试图把链接摧毁都会造成对方程序的垮台。

2.地点的udp程序,你注释任何一条客户端的sendinto,服务端都会堵塞,为啥?因为服务端有几个recvfrom就要对应多少个sendinto,哪怕是sendinto(b”)那也要有。

3.recvfrom(buffersize)如若设置每便接收数据的字节数,小于对方发送的数额字节数,如果运行Linux环境下,则只会收到到recvfrom()所设置的字节数的数量;而只要运行windows环境下,则会报错。

基于socketserver落到实处二十四线程的UDP服务端:

import socketserver

class MyUDPhandler(socketserver.BaseRequestHandler):
    def handle(self):
        client_msg,s=self.request
        s.sendto(client_msg.upper(),self.client_address)

if __name__ == '__main__':
    s=socketserver.ThreadingUDPServer(('127.0.0.1',60000),MyUDPhandler)
    s.serve_forever()
from threading import Thread
#from  multiprocessing  import  Process
import os
def talk():
    print('%s is running' %os.getpid())

if __name__ == '__main__':
    t=Thread(target=talk)
    # t=Process(target=talk)
    t.start()
    print('主',os.getpid())

2)方式二:

#开启线程
from threading import Thread
import os
class MyThread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('pid:%s name:[%s]is running' %(os.getpid(),self.name))

if __name__ == '__main__':
    t=MyThread('lln')
    t.start()
    print('主T',os.getpid())

#开启进程
from multiprocessing import Process
import os
class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('pid:%s name:[%s]is running' % (os.getpid(), self.name))
if __name__ == '__main__':
    t=MyProcess('lll')
    t.start()
    print('主P',os.getpid())

3、在一个历程下打开两个线程与在一个历程下打开几个子进度的区分

1)相比较速度:(看看hello和主线程/主进度的打印速度)

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')

2)pid的分别:(线程和主进度相同,子进程和主进程差距)

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('我的pid:',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())

3)数据是还是不是共享(线程与主进度共享数据,子进程只是将主进度拷贝过去操作的绝不相同一份数据)

from  threading import Thread
from multiprocessing import Process
def work():
    global n
    n -= 1
n = 100  #主进程数据
if __name__ == '__main__':
    # p=Process(target=work)
    # p.start()
    # p.join()
    # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了99,但改的仅仅是它自己的,查看父进程的n仍然为100

    t=Thread(target=work)
    t.start()
    t.join()
    print('主',n) #查看结果为99,因为同一进程内的线程之间共享进程内的数据

 4、练习

1)多少个义务,一个接到用户输入,一个将用户输入的始末格式化成大写,一个将格式化后的结果存入文件

from threading import Thread
msg = []
msg_fort = []
def Inp():
    while True :
        msg_l = input('>>:')
        if not msg_l : continue
        msg.append(msg_l)
def Fort():
    while True :
        if msg :
            res = msg.pop()
            msg_fort.append(res.upper())
def Save():
    with open('db.txt','a') as f :
        while True :
            if msg_fort :
                f.write('%s\n' %msg_fort.pop())
                f.flush()  #强制将缓冲区中的数据发送出去,不必等到缓冲区满
if __name__ == '__main__':
    p1 = Thread(target=Inp)
    p2 = Thread(target=Fort)
    p3 = Thread(target=Save)
    p1.start()
    p2.start()
    p3.start()

2)将前方随笔中的服务端客户端例子用多线程完毕(不打听的可以翻阅前几篇随笔)

澳门金沙国际 15澳门金沙国际 16

from threading import Thread
from socket import *
s=socket(AF_INET,SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)

def action(conn):
    while True:
        data=conn.recv(1024)
        print(data)
        conn.send(data.upper())

if __name__ == '__main__':
    while True:
        conn,addr=s.accept()
        p=Thread(target=action,args=(conn,))
        p.start()

服务端

澳门金沙国际 17澳门金沙国际 18

if __name__ == '__main__':
    while True:
        conn,addr=s.accept()
        p=Thread(target=action,args=(conn,))
        p.start()

from socket import *
s=socket(AF_INET,SOCK_STREAM)
s.connect(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if not msg:continue

    s.send(msg.encode('utf-8'))
    data=s.recv(1024)
    print(data)

客户端

5、threading模块其余方式

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

澳门金沙国际 19澳门金沙国际 20

from threading import Thread,currentThread,activeCount
import os,time,threading
def talk():
    print('%s is running' %currentThread().getName())

if __name__ == '__main__':
    # t=Thread(target=talk,name='egon')
    t=Thread(target=talk)
    t.start()
    # print(t.name)
    # print(t.getName())
    # print(t.is_alive())
    # print(currentThread().getName())
    print(threading.enumerate())
    time.sleep(3)
    print(t.is_alive())
    print('主',activeCount())

测试

主线程等其他线程

from threading import Thread,currentThread,activeCount
import os,time,threading
def talk():
    time.sleep(2)
    print('%s is running' %currentThread().getName())

if __name__ == '__main__':
    t=Thread(target=talk)
    t.start()
    t.join()
    print('主')

6、守护线程

1)守护线程和医护进度的界别

对主进度来说,运行达成指的是主进度代码运行完结

对主线程来说,运行落成指的是主线程所在的进度内装有非守护线程统统运行落成,主线程才算运行已毕

2)详细表明

主进度在其代码停止后就曾经算运行完结了(守护进度在那儿就被回收),然后主进程会平昔等非守护的子进程都运作达成后回收子进度的资源(否则会暴发僵尸进度),才会终止

主线程在其它非守护线程运行完成后才算运行已毕(守护线程在此刻就被回收)。因为主线程的竣事表示进度的收尾,进度全体的资源都将被回收,而经过必须保障非守护线程都运行完成后才能了事。

澳门金沙国际 21澳门金沙国际 22

from threading import Thread,currentThread
import os,time
def talk1():
    time.sleep(10)
    print('%s is running' %currentThread().getName())
def talk2():
    time.sleep(2)
    print('%s is running' %currentThread().getName())
def talk3():
    time.sleep(4)
    print('%s is running' %currentThread().getName())
if __name__ == '__main__':
    t1 = Thread(target=talk1)
    t2 = Thread(target=talk2)
    t3 = Thread(target=talk2)
    t1.daemon = True
    t2.daemon = True
    t1.start()
    t2.start()
    t3.start()
    print('主线程',os.getpid())

护理线程

澳门金沙国际 23澳门金沙国际 24

from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


t1=Thread(target=foo)
t2=Thread(target=bar)

t1.daemon=True
t1.start()
t2.start()
print("main-------")

迷惑人的事例


 

三、Python GIL(Global Interpreter
Lock)

1、定义:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once.

在CPython中,全局解释器锁是一个互斥锁,或GIL,它可以防止多个本地线程执行Python字节码。

This lock is necessary mainly because CPython’s memory management is not thread-safe.

这个锁是必需的,主要是因为CPython的内存管理不是线程安全的。

(However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

然而,由于GIL存在,其他的特性已经发展到依赖于它的保证。

结论:在Cpython解释器中,同一个进度下打开的三十二线程,同一时刻只好有一个线程执行,无法采用多核优势

注意:

率先需求通晓的一些是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

对协调塞尔维亚共和国(Republic of Serbia)语水平有信念的可以看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf (这篇小说透彻的解析了GIL对python三四线程的震慑)

2、GIL介绍

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本色都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只可以被一个职务所修改,进而保障数据安全。

可以一定的某些是:敬重区其他数目标河池,就应该加分歧的锁。

要想通晓GIL,首先确定一点:每一遍执行python程序,都会时有发生一个独门的长河。例如python
test.py,python aaa.py,python bbb.py会发生3个不等的python进程

'''
#验证python test.py只会产生一个进程
#test.py内容
import os,time
print(os.getpid())
time.sleep(1000)
'''
python3 test.py 
#在windows下
tasklist |findstr python
#在linux下
ps aux |grep python

验证python test.py只会产生一个进程

在一个python的进度内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾堆回收等解释器级其他线程,不问可知,所有线程都运作在这么些经过内,毫无疑问:

#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
#2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

综上:

假如多少个线程的target=work,那么执行流程是多个线程先访问到解释器的代码,即得到实施权限,然后将target的代码交给解释器的代码去实施

GIL爱戴的是讲演器级的数码,珍重用户自己的数额则必要团结加锁处理

澳门金沙国际 25澳门金沙国际 26

from threading import Thread,Lock
import time
n=100
def work():
    # mutex.acquire()
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1
    # mutex.release()

if __name__ == '__main__':
    mutex=Lock()
    t_l=[]
    s=time.time()
    for i in range(100):
        t=Thread(target=work)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
    print('%s:%s' %(time.time()-s,n))

维护自己的数据依然需求团结加锁

3、GIL与四线程

有了GIL的留存,同一时刻同一进度中唯有一个线程被实施

视听那里,你是还是不是会有疑问:进度可以拔取多核,不过付出大,而python的多线程开支小,但却无计可施利用多核优势,也就是说python没用了

要化解这么些难题,我们需求在多少个点上达标一致:

#1. cpu到底是用来做计算的,还是用来做I/O的?
#2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
#3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处 

结论:

对计量的话,cpu更多越好,但是对于I/O来说,再多的cpu也没用

理所当然对运作一个主次来说,随着cpu的增多执行功用必然会有所进步(不管提升幅度多大,总会有所进步),那是因为一个先后基本上不会是纯统计仍旧纯I/O,所以咱们不得不相对的去看一个程序到底是计量密集型仍然I/O密集型,从而进一步分析python的二十四线程到底有无用武之地

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程
#单核情况下,分析结果: 
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
#多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
#结论:
现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

4、质量测试

澳门金沙国际 27澳门金沙国际 28

from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        p=Process(target=work) #耗时5s多
        p=Thread(target=work) #耗时18s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

测算密集型:多进程成效高

澳门金沙国际 29澳门金沙国际 30

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('===>')

if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
        p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

I/O密集型:三十二线程功效高

总结:

二十四线程用于IO密集型,如socket,爬虫,web

多进程用于计算密集型,如金融分析

相关文章