Python3学习笔记_part_B

描述符使用

常使用的@classmethod、@staticmethd、@property、甚至是__slots__等属性都是通过描述符来实现的。

# 1. 模拟 @classmethod
class Imitate_classmethod(object):
    '''使用描述符模拟@classmethod'''
    def __init__(self, func):
        self.func = func
        
    def __get__(self, instance, owner):
        # 对传入的函数加工,并返回加工后的函数
        def maching_func(*args, **kwargs):
            print('函数加工后,返回实例的类')
            return self.func(owner, *args, **kwargs)
        return maching_func
    
    
class Test(object):
    msg = '这是一个对描述符模拟classmethod的测试'
    
    # 这里是一个类装饰器
    @Imitate_classmethod
    def about(cls):
        print('print: ', cls.msg)
        
    @Imitate_classmethod
    def about_var(cls, value):
        print('pirnt: {}, 传入的值为:{}'.format(cls.msg, value))

# test
t = Test()
Test.about()
Test.about_var(666)
print('\n下面是Test实例的输出:\n')
t.about()
t.about_var(777)
函数加工后,返回实例的类
print:  这是一个对描述符模拟classmethod的测试
函数加工后,返回实例的类
pirnt: 这是一个对描述符模拟classmethod的测试, 传入的值为:666

下面是Test实例的输出:

函数加工后,返回实例的类
print:  这是一个对描述符模拟classmethod的测试
函数加工后,返回实例的类
pirnt: 这是一个对描述符模拟classmethod的测试, 传入的值为:777
'''
2. 模拟 @staticmethod
  staticmethod方法与classmethod方法的区别在于:
  classmethod方法在使用需要传进一个类的引用作为参数。
  而staticmethod则不用。
'''
# 例子
class Imitate_staticmethod:
    '''
    使用描述符模拟@staticmethod
    '''
    def __init__(self, func):
        self.func = func
    def __get__(self, instance, owner):
        #对传进函数进行加工,最后返回该函数
        def machining_func(*args, **kwargs):
            print("函数加工处理后,返回实例的类")
            return self.func(*args, **kwargs)
        return machining_func

    
class Test:

    @Imitate_staticmethod
    def static_func(*args):
        print("您输入的是:",*args)
    
#测试
Test.static_func("柠檬","香蕉")
test = Test()
test.static_func(110, 112)
函数加工处理后,返回实例的类
您输入的是: 柠檬 香蕉
函数加工处理后,返回实例的类
您输入的是: 110 112
'''
3. 模拟 @property
在下面的代码中,
将描述符的回调结果存入对象字典中的好处是以后再执行函数时就不会每一次都触发描述的运行,从而提高程序的效率。
这样,我们有再执行同样的函数时,解释器会先检索对象的字典,
如果字典存在上次执行结果的值,那就不用触发描述符的运行了。

在这个实验中必须强调的一点是描述符的优先级,
我们想让程序的描述符不能覆盖实例属性就必须使用非数据描述符。所以因需求不同,描述符的优先级也不同。
'''
# 例子
class Imitate_property:
    '''使用描述符模拟property'''
    
    def __init__(self, func):
        self.func = func
        
    def __get__(self, instance, owner):
        if instance is None:
            return self
        #回调传入的函数,将运行结果保存在变量res中
        res = self.func(instance)
        #为函数名func.__name__设置一个值res后存入对象的字典中
        setattr(instance, self.func.__name__, res)
        return res

        
class Test:
    
    def __init__(self, value):
        self.value = value
        
    @Imitate_property
    def function(self):
        return self.value**2

test = Test(2)
print(test.function)  #输出:4
print(test.__dict__)  #输出:{'value': 2, 'function': 4}
print(test.function)  #输出:4
print(test.function)  #输出:4
print(test.__dict__)  #输出:{'value': 2, 'function': 4}
4
{'value': 2, 'function': 4}
4
4
{'value': 2, 'function': 4}

错误和异常

'''
try: # 在try中的异常后的代码不会执行,程序会跳转到except中对异常进行处理
    pass # 需要捕获异常的代码块
except ExceptName as result: # 捕获异常ExceptName并将异常重命名为result
    pass # 对捕获异常的处理代码块
[
except Exp1 as result1: # except 捕获语句可以有多条,不冲突即可
    pass
...
]

Exception :是异常总和,所有异常都可被此异常捕获

当捕获的异常和设定的异常名称不对应时,会进行系统异常处理。

try: # 开启捕获
    pass
except Exp: # 捕获到异常时执行
    pass
else: # 没有捕获到异常时执行
    pass
finally: # 收尾工作,不论是否捕获到异常都会执行
    pass
'''

捕获多个异常

'''
将异常名放入元组中存储
try:
    pass
except (err1, err2 [,err...]):
    pass
'''
# 异常 多个异常 例子
try:
    1/0
    open("sss0")
except NameError:
    print("try中出现NameError的异常")
except Exception as result:
    print("这里出现一个笼统的异常,{}".format(result))
else:
    print("try中没有异常时打印")
finally:
    print("不管try中是否有异常,都会执行finally")
print("异常测试结束")
# 例子结束
这里出现一个笼统的异常,division by zero
不管try中是否有异常,都会执行finally
异常测试结束

异常的嵌套

'''
内部try未捕获到异常,向外部逐层传递
try:
    try:
        pass
    except Exp1:
        pass
    pass
except Exp2:
    pass
'''

自定义异常

'''raise'''

# 用户自定义异常例子
class UserError(Exception):
    def __init__(self):
        print("这里是用户自定义的异常")
try:
    raise UserError
except UserError:
        print("抛出自定义异常")
else:
     print("没有异常")
# 例子结束
这里是用户自定义的异常
抛出自定义异常

异常处理中抛出异常

'''
try:
    Error
except exception as result:
    if (pass):
        pass # 开启捕获异常
        print(result)
    else:
        pass # 重写抛出异常,此时的异常不会被捕获,交由系统处理
        raise
'''

调试

print

最简单

断言(assert)

在程序中可以用print的地方就可以用断言(assert)

assert expression, throwException

表达式expression应该为True,否则,抛出AssertionError:throwException

python -O fileName.py 关闭断言

# assert 例子
def judger(name):
    # name!='username'为True,继续运行,False抛出your name is error!异常
    assert name != 'username','your name is error!'
    print('123456')
    
def main():
    judger('username')
    
if __name__=='__main__':
    main()
    
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-1-4fda0f972b48> in <module>()
      9 
     10 if __name__=='__main__':
---> 11     main()
     12 


<ipython-input-1-4fda0f972b48> in main()
      6 
      7 def main():
----> 8     judger('username')
      9 
     10 if __name__=='__main__':


<ipython-input-1-4fda0f972b48> in judger(name)
      2 def judger(name):
      3     # name!='username'为True,继续运行,False抛出your name is error!异常
----> 4     assert name != 'username','your name is error!'
      5     print('123456')
      6 


AssertionError: your name is error!
logging

logging 不会抛出异常,可以把调试信息输出到文件

logging 还可以指定记录信息级别(debug, info, warning, error)

指定level = INFO时,logging.debug就失效,其他级别设定同理。

logging可以通过简单的配置,一条语句同时输出到不同地方,比如:console和文件。

import logging

logging.basicConfig(level=logging.INFO)
# logging 例子
import logging
def judger(name):
    logging.info(name/0)
    print('123456')
    
def main():
    judger('username')
    
if __name__=='__main__':
    main()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-3-75192aa14816> in <module>()
     10 
     11 if __name__=='__main__':
---> 12     main()


<ipython-input-3-75192aa14816> in main()
      7 
      8 def main():
----> 9     judger('username')
     10 
     11 if __name__=='__main__':


<ipython-input-3-75192aa14816> in judger(name)
      3 def judger(name):
      4     #
----> 5     logging.info(name/0)
      6     print('123456')
      7 


TypeError: unsupported operand type(s) for /: 'str' and 'int'
pdb调试

pdb 调试有个明显的缺陷就是对于多线程,远程调试等支持得不够好,

同时没有较为直观的界面显示,不太适合大型的 python 项目。

pdb操作指令

命令 简写 功能 注解
break b 设置断点
continue c 继续执行程序
list l 查看当前行的代码
step s 进入函数
return r 执行代码直到从当前函数返回
quit q 终止并退出
next n 执行下一行
print p 打印变量的值
help h 帮助
args a 查看传入参数
  回车 重复上一条命令
break b 显示所有断点
break lineno b lineno 在指定行设置断点
break file:lineno b file:lineno 在指定文件的行设置断点
clear num   删除指定断点 这里的num不是添加断点的行号,而是断点的序号
bt   查看函数调用栈帧

pdb交互调试

import pdb

pdb.run(funcName(attribute)) # 这里需要在pdb中先按s,然后才可以l,不然不会显示代码

pdb程序里埋点

import pdb

pdb.set_trace() # 当程序遇到这句话时才进入调试

垃圾回收

参考网址:https://www.cnblogs.com/jin-xin/articles/9439483.html

概括:

同一代码块下:缓存机制
不同代码块:小数据池

代码块

一个模块、一个函数、一个类、一个文件都是一个代码块。

在交互式命令行下,一个命令就是一个代码块。

代码块的缓存机制

Python在执行同一个代码块的初始化对象的命令时,

会检查是否其值是否已经存在,如果存在,会将其重用。

换句话说:

执行同一个代码块时,遇到初始化对象的命令时,

他会将初始化的这个变量与值存储在一个字典中,

在遇到新的变量时,会先在字典中查询记录,

如果有同样的记录那么它会重复使用这个字典中的之前的这个值。

满足缓存机制则他们在内存中只存在一个,即:id相同。

代码块的缓存机制的适用范围: int(float),str,bool。

int(float):任何数字在同一代码块下都会复用。

bool:True和False在字典中会以1,0方式存在,并且复用。

str:几乎所有的字符串都会符合缓存机制
        
        1,非乘法得到的字符串都满足代码块的缓存机制
        
        2,乘法得到的字符串分两种情况:

              2.1 乘数为1时,任何字符串满足代码块的缓存机制:
                
              2.2 乘数>=2时:仅含大小写字母,数字,下划线,
              总长度<=20,满足代码块的缓存机制

优点:

能够提高一些字符串,整数处理人物在时间和空间上的性能;
需要值相同的字符串,整数的时候,直接从‘字典’中取出复用,避免频繁创建和销毁,提升效率,节约内存。
'''jupyter这里运行存在问题,以注释为主'''

# 字符串缓存机制 例子

# 非乘法得到的字符串
def unMult():
    s1 = 'string'
    s2 = 'string'
    print('非乘法得到的字符串')
    print('*'*30)
    print('s1和s2地址比较:',id(s1) == id(s2)) # True
    print('*'*30)

# 乘数为1得到的字符串
def multOne():
    s3 = 'asdf!@#$%^*'*1
    s4 = 'asdf!@#$%^*'*1
    print('乘数为1得到的字符串')
    print('*'*30)
    print('s3和s4地址比较:',id(s3) == id(s4)) # True
    print('*'*30)
   
# 乘数>=2时
def multMore():
    s3 = 'adf!'*4 # 包含其他字符,总长<20
    s4 = 'adf!'*4
    s5 = 'qwertyuiop[]asdfghjk!@#$%^&'*4
    # 包含其他字符,总长>20
    s6 = 'qwertyuiop[]asdfghjk!@#$%^&'*4
    s7 = 'asdf_asdf'*3 # # 不包含其他字符,总长<20
    s8 = 'asdf_asdf'*3
    
    print('乘数为>=2,总长度<=20,包含其他字符得到的字符串')
    print('*'*30)
    print('s3和s4地址比较:',id(s3) == id(s4)) # False
    print('*'*30)
    
    print('乘数为>=2,总长度>20,包含其他字符得到的字符串')
    print('*'*30)
    print('s5和s6地址比较:',id(s5) == id(s6)) # False
    print('*'*30)
    
    print('乘数为>=2,总长度<=20,不包含其他字符得到的字符串')
    print('*'*30)
    print('s7和s8地址比较:',id(s7) == id(s8)) # True
    print('*'*30)
    
# test
unMult()
multOne()
multMore()
非乘法得到的字符串
******************************
s1和s2地址比较: True
s1和s2来源比较: True
******************************
乘数为1得到的字符串
******************************
s3和s4地址比较: True
s3和s4来源比较: True
******************************
乘数为>=2,总长度<=20,包含其他字符得到的字符串
******************************
s3和s4地址比较: True
s3和s4来源比较: True
******************************
乘数为>=2,总长度>20,包含其他字符得到的字符串
******************************
s5和s6地址比较: True
s5和s6来源比较: True
******************************
乘数为>=2,总长度<=20,不包含其他字符得到的字符串
******************************
s7和s8地址比较: True
s7和s8来源比较: True
******************************

小数据池

大前提:

小数据池也是只针对 int(float),str,bool。

小数据池是针对不同代码块之间的缓存机制!!!

Python自动将-5~256的整数进行了缓存,

当你将这些整数赋值给变量时,并不会重新创建对象,

而是使用已经创建好的缓存对象。

python会将一定规则的字符串在字符串驻留池中创建一份。

'''小数据池例子'''

# 整数
a = 100
b = 100
c = 123456
d = 123456

print('id(a) == id(b): ',id(a) == id(b))
print('id(c) == id(d): ',id(c) == id(d))

id(a) == id(b):  True
id(c) == id(d):  False

指定驻留

# 指定驻留 例子
from sys import intern

a = 'asd*&'*3
b = 'asd*&'*3

print('未指定驻留:a is b:', a is b)
print(id(a))
print(id(b))

c = intern('asd*&'*3)
d = intern('asd*&'*3)

print('不未指定驻留:c is d:', c is d)
print(id(c))
print(id(d))
未指定驻留:a is b: False
139839647396976
139839643255088
不未指定驻留:c is d: True
139839642805680
139839642805680

引用计数

Python中以引用计数为主:

引用计数优点:简单,实时性

引用计数缺点:维护时占用内存,循环引用,造成内存泄漏

引用计数减一不止del,还可以透过赋值None

隔代回收

Ruby中的垃圾回收是标记清除,对内存的申请是一次申请多个,
当内存不够用时再清理,而Python中的内存申请是用一个申请一个。

隔代回收为辅:

零代链表中检测到相互循环引用的减一,得到是否是垃圾(引用计数为0),需要回收,否则不会减一
GC模块
'''
gc.get_count():获取当前自动执行垃圾回收的计数器

返回一个元组(x,y,z)
参数解释:
[
x 表示内存占用,
y 表示零代清理次数,
z 同理表示1代清理次数
]


gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率

返回(x,y,z) 

参数解释:
[
x表示清理上限,
y表示每清理零代链表10次,
清理一次 1 代链表。
z同理是1,2代 这里的x,y,z默认值为(700,10,10)
]

查看一个对象的引用计数:

sys.getrefcount()

gc.disable() 关闭Python的gc

gc.enable() 开启gc

gc.isenabled() 判断是否是开启gc的

gc.collect([generation])显式进行垃圾回收(手动回收)

可以输入参数:
[
0代表只检查第一代的对象,

1代表检查一,二代的对象,

2代表检查一,二,三代的对象,
如果不传参数,执行一个full collection,也就是等于传2。
]


注意:
尽量不要手动重写对象的__del__方法,
因为重写后会使删除时不会自动调用gc来删除,
此时需要调用父类的__del__()来删除
'''

# gc 引用计数 示例
import gc, sys

a = 1
get_a_c = sys.getrefcount(a)
b = a
get_b_c = sys.getrefcount(a)

gc_c = gc.get_count()
gc_t = gc.get_threshold()

gc.disable()
gc.enable()

gc_is = gc.isenabled()

gc_cl = gc.collect(2)

print(get_a_c, get_b_c, gc_c, gc_t, gc_is, gc_cl)
2275 2275 (603, 5, 0) (700, 10, 10) True 921

内置方法

参考网址:http://www.runoob.com/python3/python3-built-in-functions.html

'''
range(start, stop, step)
Python2中直接生成,Python3中生成器的生成方式

xrange():<Python3中重命名为range了>
Python2中:功能和range()一样,但是是生成器形式的,需要next()调用

map(function, sequence[, sequence]) 返回list
参数解释:
[
function 一个函数

sequence 一个或多个序列, 取决于function需要几个参数

这里参数序列中的每一个元素分别调用function,
返回包含每次function的函数返回值的list
]
'''
# map功能演示
a = map(lambda x:x+x, [y for y in range(1,5,1)])
'''对list [1,2,3,4]中的每一个元素进行自身叠加操作'''
print(a)
for i in a :
    print(i) # 输出叠加结果
<map object at 0x0000016E3AE1C3C8>
2
4
6
8
'''
filter(function, iterable) 用于过滤序列
参数解释:
[
function 判断函数,返回True或False,
iterable 可迭代对象
]
'''
# filter 过滤奇数(不可被2整除)
def is_odd(num):
    '''判断函数,返回值为bool类型'''
    return num % 2 == 1

l = [1,2,3,4,5,6,7,8,9,10]

newlist = filter(is_odd, l)
print('过滤结果:newlist: ', newlist)
print('得到奇数列表:', list(newlist))
过滤结果:newlist:  <filter object at 0x0000016E3AEA0C88>
得到奇数: [1, 3, 5, 7, 9]
'''
reduce(function, iterable[, initial]) 对可迭代对象进行连续累积操作,
可以加减乘除等
参数解释:
[
function(x,y) 处理函数,有两个参数
iterable 可迭代对象
initial 可选参数,初始参数
]

function 同样可是是lambda函数

在Python3中,reduce合并到functools里

'''
# Python3 reduce()使用 例子:计算iterable的累加
from functools import reduce # 导入reduce

def add(x, y):
    '''定义处理函数:加操作'''
    return x + y

numlist = [1,2,3,4,5,6] # 可迭代对象,要处理的对象

sum = reduce(add, numlist) # 对numlist实现累加操作

print(numlist,'累加结果:',sum)

# lambda函数实现累乘
produce = reduce(lambda x,y: x*y, numlist) # 结果为720

print(numlist,'累乘结果:',produce)
[1, 2, 3, 4, 5, 6] 累加结果: 21
[1, 2, 3, 4, 5, 6] 累乘结果: 720
'''
sorted(iterable, key=None, reverse=False) 对可迭代对象进行排序(升序[A-Z])
参数解释:
[
iterable 可迭代对象

key 主要用来比表的元素,具体可取自可迭代对象中,
指定可迭代对象的一个元素

reverse 排序规则,reverse=True,降序[Z-A],reverse=False,升序[A-Z](默认)
]

sort() 应用在list中
sorted() 应用在所有可迭代对象
'''
# sort() sorted() 例子
alist = [1,2,45,3,12,6,33,44]

astring = 'asdfqwer1'

alist.sort()

sorted_str = sorted(astring)

print('sort(): ',alist,'\nsorted(): ', sorted_str)

sort():  [1, 2, 3, 6, 12, 33, 44, 45] 
sorted():  ['1', 'a', 'd', 'e', 'f', 'q', 'r', 's', 'w']

pep8规则

pep8官网规范地址

https://www.python.org/dev/peps/pep-0008/

每级缩进 4个空格

函数中的形参过长,换行时建议对齐

类间隔两个空行

方法间隔一个空行

导入在单独行

import os
import sys

导库:标准库,第三方,本地(个人)

禁止使用通配符导入

类名,驼峰命名,模块,全程小写,可以用下划线

逗号,冒号,分号之前避免空格

赋值等操作符前后不能因为对齐而添加多个空格

二元运算符两边放置一个空格

关键字参数和默认值参数的前后不要加空格

索引操作中的冒号当作操作符处理前后要有同样的空格(一个空格或者没有空格)

函数调用的左括号之前不能有空格

最大行宽

限制所有行的最大行宽为79字符。

文本长块,比如文档字符串或注释,行长度应限制为72个字符。

进程

进程定义

参考网址:https://www.cnblogs.com/gengyi/p/8564052.html

‘'''
进程:

进程是程序的一次动态执行过程,它对应了从代码加载、
执行到执行完毕的一个完整过程。

进程是系统进行资源分配和调度的一个独立单位。
进程是由代码(堆栈段)、数据(数据段)、内核状态和一组寄存器组成。 

在多任务操作系统中,通过运行多个进程来并发地执行多个任务。
由于每个线程都是一个能独立执行自身指令的不同控制流,
因此一个包含多个线程的进程也能够实现进程内多任务的并发执行。 

进程是一个内核级的实体,进程结构的所有成分都在内核空间中,
一个用户程序不能直接访问这些数据。 


进程的状态: 

创建、准备、运行、阻塞、结束。


进程间的通信方式:

    (1)管道(pipe)及有名管道(named pipe):
                
                管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

    (2)信号(signal):
    
        信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,
        用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

    (3)消息队列(message queue):
        
        消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,
        具有写权限的进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限的进程则可以从消息队列中读取信息。

    (4)共享内存(shared memory):
        
        可以说这是最有用的进程间通信方式。
        它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。
        这种方式需要依靠某种同步操作,如互斥锁和信号量等。

    (5)信号量(semaphore):
    
        主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

    (6)套接字(socket):
        这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

Python中进程创建:

    os.fork()

    subprocess

    processing

    multiprocessing
'''

创建进程

linux专供版

这里的fork()只是用于Linux(unix)仅作为了解

不要在jupyter notebook中进行大量或多次fork执行,会大量占用cpu,导致卡顿

fork 侧重于父子进程都有任务,适合于少量的子进程创建

父进程、子进程执行顺序没有规律,完全取决于操作系统的调度算法

import os

pid = os.fork()

fork()是唯一调用一次返回两次的函数,因为操作系统将父进程复制一份形成新的进程(子进程),然后分别在父进程和子进程内返回。

父进程和子进程都会从fork()函数中得到一个返回值,子进程永远返回0,而父进程返回子进程的ID。

一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

通过os.getpid()获取当前进程的pid通过os.getppid()获取当前进程父进程的pid

这里可以把fork理解为鸣人影分身的deepcopy模式,完全克隆出一个自己,对分分支后的任务进行执行

fork理解

# fork() 例子
import os # fork()在os模块下

def prt():
    print('我在fork前')
    
prt()

def use_fork():
    pid = os.fork() # 进程在这里fork,产生一个新的进程,返回新进程的pid

    if pid<0:
        print('fork调用失败')

    elif pid == 0: # 子进程
        print('我是子进程<{}>,我的父进程为:<{}>'.format(os.getpid(), os.getppid()))
        # 打印子进程pid,父进程pid

    else: # 父进程
        print('我是父进程<{}>,我的子进程为<{}>'.format(os.getpid(), pid))
        # 打印父进程pid,子进程pid

    print('父子进程都可以执行这行') # 分支执行结束,回归执行程序主干

use_fork()
我在fork前
我是父进程<4134>,我的子进程为<5573>
父子进程都可以执行这行
我在fork前
我是子进程<5573>,我的父进程为:<4134>
父子进程都可以执行这行
'''多进程中,每个进程中所有数据(包括全局变量)都各有拥有一份,互不影响'''
# fork() 内修改全局变量 例子
import os # fork()在os模块下

num = 12
print('未fork的全局变量:', num)
pid = os.fork() # 进程在这里fork,产生一个新的进程,返回新进程的pid

if pid<0:
    print('fork调用失败')

elif pid == 0: # 子进程
    print('我是子进程<{}>,我的父进程为:<{}>'.format(os.getpid(), os.getppid()))
    num += 3
    print('子进程修改num为:{}'.format(num))
    
else: # 父进程
    print('我是父进程<{}>,我的子进程为<{}>'.format(os.getpid(), pid))
    num += 5
    print('父进程修改num为:{}'.format(num))
    
print('num:',num) # 分支执行结束,回归执行程序主干
未fork的全局变量: 12
我是父进程<6203>,我的子进程为<6301>
父进程修改num为:17
num: 17
我是子进程<6301>,我的父进程为:<6203>
子进程修改num为:15
num: 15
'''多个fork()同时出现'''
# 多个fork()同时出现
import os, time

ret = os.fork()
if ret ==0:
    print("1".center(10, "*"))
else:
    print("2".center(10,"*"))
ret = os.fork()
if ret == 0:
    print("11".center(10,"-"))
else:
    print(print("22".center(10,"+")))
    
# 父进程、子进程执行顺序没有规律,完全取决于操作系统的调度算法

# 注意:这里的结果中,包含了两个11,22
# 这里和前边说的在fork()处分开后,代码都会分别执行,
# 也就是后边的fork()是在单个进程中的再一次分进程了
****1*****
****2*****
++++22++++
None
++++22++++
****2*****
----11----
None
----11----

getpid、getppid

getpid()获得当前进程的pid

getppid()获得当前进程的父进程

在新建进程之后子进程和父进程执行的基本一样(这里只是不执行父进程专有的代码,其他的代码子进程同样会执行)

全局变量在多个进程中不共享

# getpid getppid 例子
import os, time

g_num = 666
tmp = os.fork()
if tmp == 0:
    print("*"*20)
    print(g_num)
    g_num +=111
    time.sleep(2)
    print("this is son pid = {0}, ppid = {1}, g_num_id = {2}, g_num = {3}".format(os.getpid(), os.getppid(), id(g_num), g_num))
else:
    print("*"*20)
    print(g_num)
    print("this is father pid = {0}, ppid = {1}, g_num_id = {2}, g_num = {3}".format(os.getpid(), os.getppid(), id(g_num), g_num))
********************
666
this is father pid = 6203, ppid = 6087, g_num_id = 140675625067056, g_num = 666
********************
666
this is son pid = 6941, ppid = 6203, g_num_id = 140675685461968, g_num = 777
multiprocessing

multiprocessing模块就是跨平台版本的多进程模块。

multiprocessing模块提供了一个Process类来代表一个进程对象

代码if __name__=='__main__':在Process使用中必须有

'''Process 创建子进程'''

from multiprocessing import Process

import os

# 子进程要执行的代码
def run_proc(name):
    print('子进程运行中,name= %s ,pid=%d...' % (name, os.getpid()))

if __name__=='__main__': # 判断是否在当前模块下
    print('父进程 %d.' % os.getpid())
    p = Process(target=run_proc, args=('test',)) # 创建子进程
    print('子进程将要执行')
    p.start()
    p.join()
    print('子进程已结束')
父进程 6203.
子进程将要执行
子进程运行中,name= test ,pid=7030...
子进程已结束
'''
对例子的说明:

创建子进程时,只需要传入一个执行函数和函数的参数,
创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。

join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
'''
Process
'''
Process 语法:
Process([group[, target[, name[, args[, kwargs]]]]])

参数解释:
[
    group: 线程组,目前还没有实现,库引用中提示必须是None; 
    target: 要执行的方法; 进程实例所调用对象
    name: 进程名; 当前进程的别名
    args/kwargs: 要传入方法的参数(参数元组/参数字典)。
]


Process类常用方法:

is_alive() 判断进程实例是否还在执行

join([timeout]) 是否等待进程实例执行结束,timeout为等待超时时间毫秒级

start() 启动进程实例(创建子进程)

run() 没有给定target参数,对指定对象调用start()方法时,将执行run()方法

terminate() 不管任务是否完成,立即终止


Process 属性:

authkey

daemon:守护进程标识,在start()调用之前可以对其进行修改 

exitcode:进程的退出状态码(进程运行时为None,-N表示被信号N结束)

name:进程名

pid:进程号
'''
# 例子
from multiprocessing import Process

import os

from time import sleep

# 子进程执行代码
def run_proc(name, age, **kwargs):
    for i in range(10):
        print('子进程运行中,i = {}, name = {}, age = {}, pid = {}'.format(
            i, name, age, os.getpid()
        ))    
        print(kwargs)
        sleep(0.5) # 挂起0.5s
    
if __name__=='__main__':
    print('父进程:',os.getpid())
    p = Process(target=run_proc, args=('test',22), kwargs={'num':123456})
    print('子进程将执行')
    p.start()
    sleep(1) # 挂起1s
    p.terminate()
    p.join()
    print('子进程结束')
父进程: 6203
子进程将执行
子进程运行中,i = 0, name = test, age = 22, pid = 7697
{'num': 123456}
子进程运行中,i = 1, name = test, age = 22, pid = 7697
{'num': 123456}
子进程结束
# process 例子2

from multiprocessing import Process

import time

import os

#两个子进程将会调用的两个方法
def worker_1(interval):
    print('worker_1, 父进程<{}>,当前进程<{}>'.format(os.getppid(), os.getpid()))
    t_start = time.time()
    time.sleep(interval) # 挂起interval秒
    t_end = time.time()
    print('worker_1执行时间为{}'.format(t_end-t_start))
    
def worker_2(interval):
    print('worker_2, 父进程<{}>,当前进程<{}>'.format(os.getppid(), os.getpid()))
    t_start = time.time()
    time.sleep(interval) # 挂起interval秒
    t_end = time.time()
    print('worker_2执行时间为{}'.format(t_end-t_start))
    
# 输出当前程序的id
print('进程pid:', os.getpid())

# 创建两个进程对象,target指向进程对象要执行的方法,
# args后的元组是要传递给work_1()中的interval参数
# 若不指定name参数,默认的进程对象名称为Process-N,N为一个递增的整数
p1=Process(target=worker_1,args=(3,))
p2=Process(target=worker_2,name='Dog',args=(1,))

# 使用<进程对象.start()>来创建并执行一个子进程
# 创建的两个进程在start后分别执行worker_1和worker_2中的代码
p1.start()
p2.start()

# 同时父进程仍然向下执行,若p2进程还在执行,将返回Ture
print('p2.isalive=',p2.is_alive())

# 输出p1和p2进程的别名和pid
print('p1.name={}\tp1.pid={}'.format(p1.name, p1.pid))
print('p2.name={}\tp2.pid={}'.format(p2.name, p2.pid))


# join() timeout不设置,表示父进程要在这个位置等待p1
# 进程执行完成后再执行下面的语句

p1.join(1) # 设置超时时间为1s
print('set timeout=1, p1.is_alive=',p1.is_alive()) # 这里应该打印True

# 一般用于进程间的数据同步,若不写这一句,下面的is_alive判断将为True,
# 在shell(cmd)中调用此程序可以完整的看到这个过程
p1.join() # 设置等待子进程执行完毕再执行父进程的后续语句
print('p1.is_alive=',p1.is_alive())
# 等待子进程执行完毕,此处打印可能有输出延迟

进程pid: 6203
worker_1, 父进程<6203>,当前进程<8048>
worker_2, 父进程<6203>,当前进程<8049>
p2.isalive= True
p1.name=Process-12  p1.pid=8048
p2.name=Dog p2.pid=8049
worker_2执行时间为1.0010364055633545
set timeout=1, p1.is_alive= True
worker_1执行时间为3.002727746963501
p1.is_alive= False
Process 子类

创建新的进程还能够使用类的方式:

可以自定义一个类,继承Process类,

每次实例化这个类的时候,就等同于实例化一个进程对象

用Process子类来进行进程的控制可以达到简化的效果

# Process 子类 例子
from multiprocessing import Process

import time

import os

# 继承Process类
class ProcessSon(Process):
    # Process类本身有__init__方法,重写Process的__init__方法,会有一个问题
    # 并没有完全初始化一个Process类,所以不能使用这个类继承的一些属性和方法
    # 最好的方法是将继承类本身传递给Process.__init__方法,完成对自雷的初始化
    def __init__(self, interval):
        Process.__init__(self)
        self.interval = interval
        
    # 重写Process 的run()方法
    def run(self):
        print('子进程{}开始执行,父进程为{}'.format(os.getpid(), os.getppid()))
        t_start = time.time()
        time.sleep(self.interval)
        t_stop = time.time()
        print('进程{}执行结束,用时{}'.format(os.getpid(), t_stop-t_start))
        
if __name__=='__main__': # Process 标配
    t_start = time.time()
    print('当前进程为:{}'.format(os.getpid()))
    p1=ProcessSon(3) # 用子类创建一个新的进程对象
    # 对一个不包含target属性的Process类执行start()方法,会执行此类的run()方法,
    # 这里执行的是p1.run()[也就是ProcessSon.run()]
    p1.start()
    p1.join()
    t_stop = time.time()
    
    print('进程{}执行结束,用时{}\nover'.format(os.getpid(), t_stop-t_start))
    
当前进程为:2769
子进程25847开始执行,父进程为2769
进程25847执行结束,用时3.0029664039611816
进程2769执行结束,用时3.0183212757110596
over

进程池Pool

需要创建的子进程数量不多时,可以直接用multiprocessing.Process动态生成多个进程,但是对于数目过大时,手动创建工作量太大,可以用multiprocessing模块中的Pool方法

初始化Pool时可指定最大进程数,当有新的请求提交到Pool时,在Pool未满下,会创建新进程来执行请求,但若Pool已满,则请求会等待,直到进程池有空位才会创建新的进程来执行请求

Pool进程池并不是进程数越大越好

# Pool 例子
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
from multiprocessing import Pool;

import os

import time

import random

run_start = time.time()

def worker(msg):
    t_start = time.time()
    print('{}开始执行,进程号为:{}'.format(msg, os.getpid()))
    # random.random()随机生成0-1之间的浮点数
    sleep_num = random.random()*2
    time.sleep(sleep_num)
    t_stop = time.time()
    use_time = t_stop-t_start
    print('{}执行完毕,用时{}'.format(msg, use_time))
    
print('start'.center(30, '*'))

po = Pool(3) # 定义一个进程池,设置最大进程数为3

for i in range(10):
    # Pool.apply_async(要调用的目标,(传递给目标的参数元组,))
    # 每次循环将会用空闲出的子进程去调用目标
    po.apply_async(worker, (i, )) # 运行worker函数,并且worker传递参数为i
    
po.close() # 关闭进程池,关闭后po不再接受新的请求

# 进程阻塞,如果不添加此句,会出现主进程执行结束后直接关闭,
# 子进程无法执行
po.join() # 等待po中所有子进程执行完成,必须放在close语句后
print('end'.center(30, '*'))

run_stop = time.time()
run_time = run_stop-run_start
print('总用时:',run_time)
************start*************
0开始执行,进程号为:9229
1开始执行,进程号为:9230
2开始执行,进程号为:9231
2执行完毕,用时0.08111715316772461
3开始执行,进程号为:9231
3执行完毕,用时0.25066304206848145
4开始执行,进程号为:9231
1执行完毕,用时0.7374343872070312
5开始执行,进程号为:9230
4执行完毕,用时0.934847354888916
6开始执行,进程号为:9231
0执行完毕,用时1.3051488399505615
7开始执行,进程号为:9229
5执行完毕,用时0.7723855972290039
8开始执行,进程号为:9230
8执行完毕,用时0.9804284572601318
9开始执行,进程号为:9230
6执行完毕,用时1.6846919059753418
7执行完毕,用时1.6566157341003418
9执行完毕,用时1.085120439529419
*************end**************
总用时: 3.6389713287353516
multiprocessing.Pool常用函数

apply_async(func[, args[,kwds]])

使用非阻塞方式调用func(并行执行,都塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键子参数列表;

apply(func[, args[, kwds]])

使用阻塞的方式调用func

close()

关闭Pool,使其不再接受新的任务

terminate()

强制终止任务,不管是否完成

join()

 主进程阻塞,等待子进程的退出,必须在close()或terminate()后使用
'''apply阻塞式'''
# Pool 例子
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
from multiprocessing import Pool;

import os

import time

import random


run_start = time.time()

def worker(msg):
    t_start = time.time()
    print('{}开始执行,进程号为:{}'.format(msg, os.getpid()))
    # random.random()随机生成0-1之间的浮点数
    sleep_num = random.random()*2
    time.sleep(sleep_num)
    t_stop = time.time()
    use_time = t_stop-t_start
    print('{}执行完毕,用时{}'.format(msg, use_time))
    
print('start'.center(30, '*'))

po = Pool(3) # 定义一个进程池,设置最大进程数为3

for i in range(10):
    # Pool.apply_async(要调用的目标,(传递给目标的参数元组,))
    # 每次循环将会用空闲出的子进程去调用目标
    po.apply(worker, (i, )) # 运行worker函数,并且worker传递参数为i
    
po.close() # 关闭进程池,关闭后po不在接受新的请求

# 进程阻塞,如果不添加此句,会出现主进程执行结束后直接关闭,
# 子进程无法执行
po.join() # 等待po中所有子进程执行完成,必须放在close语句后
print('end'.center(30, '*'))

run_stop = time.time()
run_time = run_stop-run_start
print('总用时:',run_time)
************start*************
0开始执行,进程号为:9181
0执行完毕,用时0.21720004081726074
1开始执行,进程号为:9182
1执行完毕,用时1.266624927520752
2开始执行,进程号为:9183
2执行完毕,用时0.8020675182342529
3开始执行,进程号为:9181
3执行完毕,用时1.9233331680297852
4开始执行,进程号为:9182
4执行完毕,用时0.48862147331237793
5开始执行,进程号为:9183
5执行完毕,用时0.8913943767547607
6开始执行,进程号为:9181
6执行完毕,用时0.03457212448120117
7开始执行,进程号为:9182
7执行完毕,用时0.5286092758178711
8开始执行,进程号为:9183
8执行完毕,用时0.452136754989624
9开始执行,进程号为:9181
9执行完毕,用时1.9772589206695557
*************end**************
总用时: 8.642189741134644

进程间的通信Queue

可以使用multiprocessing模块的Queue实现多进程之间的数据传递,

Queue本身是一个消息列队程序

# Queue 例子
from multiprocessing import Queue

q = Queue(3) # 初始化一个Queue对象,最多接收三条put消息
q.put('msg1')
q.put('msg2')
print(q.full()) # False
q.put('msg3')
print(q.full()) # True

# 消息队列已满,抛出异常,
# 第一个try会等待2s后再抛出,
# 第二个try会立刻抛出异常
try:
    q.put('msg4',True, 2)
except:
    print('消息队列已满,现有消息数量:{}'.format(q.qsize()))

try:
    q.put_nowait('msg4')
except:
    print('消息队列已满,现有消息数量:{}'.format(q.qsize()))
    
# 推荐
# 先判断消息队列是否已满,再写入
if not q.full():
    q.put_nowait('msg4')

# 读取消息时,先判断消息队列是否为空,再读取
if not q.empty():
    for i in range(q.qsize()):
        print(q.get_nowait())
False
True
消息队列已满,现有消息数量:3
消息队列已满,现有消息数量:3
msg1
msg2
msg3
Queue使用

初始化Queue()对象时(如:q = Queue()),若Queue中未指定值,或为负值,表示可接受消息数列无上限(直到内存存满)

Queue.qsize()

返回当前队列包含的消息数量

Queue.empty()

消息队列判空,队列为空返回True,队列已满返回False

Queue.full()

消息队列判满,已满,返回True,否则返回False

Queue.get([block[, timeout]])

获取队列的一条消息,然后将其从队列中移除,block默认值为True;

block参数设置:

    1. 如果block使用默认值,且没有设置timeout(单位秒),消息队列如果已经没有空间写入,此时程序将被阻塞(停在写入状态),直到从消息队列腾出空间为止,若设置了timeout,则会等待timeout秒,若还没读取到任何消息,抛出”Queue.Empty“异常;
    
    2. 若block值为False,消息队列为空,会立刻抛出”Queue.Empty“异常

Queue.get_nowait()

相当于`Queue.get(False)`

Queue.put(item[, block[, timeout]])

参数解释:
[
    item:将要写入消息队列的消息
    
    block:阻塞设置
        
        block使用默认值(True),且未设置timeout(单位秒),消息队列已满,此时程序将被阻塞,直到消息队列有空间为止,若设置了timeout,等待超时后会抛出”Queue.full“异常
        
        block=False,消息队列已满,会立刻抛出”Queue.Full”异常
    
    timeout:超时时间
]

Queue.put_nowait(item)

相当于Queue.put(item,False)
# Queue 实例
# 这里注意:py文件名一定不能和Python中的保留名相同,不然会报错

from multiprocessing import Process, Queue

import os,time, random

# 写数据进程执行
def write(q):
    for value in ['A', 'B', 'C']:
        print('put {} to queue'.format(value))
        q.put(value)
        time.sleep(random.random())
        
# 读数据操作
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('get {} from queue'.format(value))
            time.sleep(random.random())
            
        else:
            break
            
if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程
    q = Queue()
    pw = Process(target=write, args=(q, ))
    pr = Process(target=read, args=(q, ))
    
    # 启动子进程pw,写入消息
    pw.start()
    # 等待子进程pw结束(加入进程阻塞)
    pw.join()
    # 启动读消息子进程pr
    pr.start()
    pr.join(5) # 针对read(q)的while设置,防止等待过长
    # 设置超时时间
    pw.terminate()
    print('\n所有数据读写完成!')
    

put A to queue
put B to queue
put C to queue
get A from queue
get B from queue
get C from queue

所有数据读写完成!
进程池中的Queue

Pool中使用Queue:

使用的是multiprocessing.Manager()中的Queue(),不是multiprocessing.Queue() 否则会报错“RuntimeError: Queue objects should only be shared between processes through inheritance.”
# Pool中使用Queue通信
# pool中使用的是multiprocessing.Manager的Queue
from multiprocessing import Manager, Pool

import os, time, random


def reader(q):
    print('reader <{}> run, father is <{}>'.format(os.getpid(), os.getppid()))
    
    for i in range(q.qsize()):
        print('reader get msg from Queue', q.get(True))
        
def writer(q):
    print('writer <{}> run, father is <{}>'.format(os.getpid(), os.getppid()))
    
    for i in range(5):
        q.put(i)
        
if __name__=='__main__':
    print('pid:{} start'.format(os.getpid()))
    q = Manager().Queue() # 消息队列初始化使用的是Manager().Queue()
    po = Pool()
    
    # 阻塞模式创建进程,在writer()完全执行完后再执行reader(),
    # 免去reader()中的死循环
    po.apply(writer, (q, ))
    po.apply(reader, (q, ))
    
    po.close() # 关闭Pool
    
    po.join() # 进程阻塞
    
    print('pid:{} end'.format(os.getpid()))
    
    
pid:2126 start
writer <3475> run, father is <2126>
reader <3477> run, father is <2126>
reader get msg from Queue 0
reader get msg from Queue 1
reader get msg from Queue 2
reader get msg from Queue 3
reader get msg from Queue 4
pid:2126 end
# 多进程拷贝文件
#-*- coding:utf-8 -*-
from multiprocessing import Pool, Manager

import os, shutil # 刚好和os模块互补

def cp_file(name, oldFolderName, newFolderName, queue):
    '''将旧文件夹中的文件拷贝到新文件夹'''
    with open(os.path.join(oldFolderName,name), 'r+', encoding='utf-8') as fr:
        content = fr.read()
        
    with open(os.path.join(newFolderName, name), 'w', encoding='utf-8') as fw:
        fw.write(content)
        
    queue.put(name)

def main(): 
    # 获取文件要copy的文件夹的名字
    oldFolderName = input('input Folder Name:')
    # 新建一个文件夹
    newFolderName = oldFolderName+'-复件'
    # 判断文件夹是否存在
    if os.path.exists(newFolderName):
        # 存在就删除后再新建
        shutil.rmtree(newFolderName)
        os.mkdir(newFolderName)
    
    # 获取需要copy的文件夹中的所有文件名
    fileNames = os.listdir(oldFolderName)
    # 使用多进程方式copy文件
    pool = Pool(6)
    queue = Manager().Queue()
    
    for name in fileNames:
        pool.apply_async(cp_file, args=(name, oldFolderName, newFolderName, queue))
    num = 0
    all_num = len(fileNames)
    while num<all_num:
        queue.get()
        num +=1
        copyRate = num/all_num
        print('\rcopy的进度是{:.2f}%'.format((copyRate*100)), end='')
        
if __name__=='__main__':
    main()
        
# 僵尸进程:父进程死了,但是子进程没死,也叫孤儿进程
input Folder Name:multiprocessing_test
copy的进度是100.00%

线程

线程是应用程序中工作的最小单元。

线程可以被抢占(中断)

在其他线程正在运行时,线程可以暂时搁置(睡眠),这是线程的退让。

线程的执行顺序由操作系统的调度算法决定

线程调度

线程调度

线程可以分为:

内核线程:由操作系统内核创建和撤销。

用户线程:不需要内核支持而在用户程序中实现的线程。

Python使用线程:

函数:_thread模块 `_thread.start_new_thread(function, args[, kwargs])`
不建议使用,推荐使用threading

类:Thread类
# 未使用模块的单线程 例子
import time


def about():
    print('我是单线程,我就是个弟弟')
    time.sleep(0.5)
    
if __name__=='__main__':
    for i in range(5):
        about()
我是单线程,我就是个弟弟
我是单线程,我就是个弟弟
我是单线程,我就是个弟弟
我是单线程,我就是个弟弟
我是单线程,我就是个弟弟
'''
调用thread模块的函数式单线程
import _thread

_thread.start_new_thread(fucntion, args[, kwargs])

参数说明:
[
    funciton 线程调用函数,
    
    args 传入被调用线程函数的参数,必须为tuple类型“(1,)”
    
    kwargs 可选参数,dict类型(关键字参数)
]
'''
# thread 线程例子
import _thread
import time

# 定义调用函数
def print_time(threadName, delay):
    count = 0
    while count<3:
        time.sleep(delay)
        count +=1
        print('线程:{}, 时间:{}'.format(threadName, time.ctime(time.time())))
        
# 创建两个线程
try:
    _thread.start_new_thread(print_time, ('Thread-1', 1))
    _thread.start_new_thread(print_time, ('Thread-2', 3))
except Exception:
    print("Error:can't start thread" )
    
线程:Thread-1, 时间:Fri Jan 11 16:31:51 2019
线程:Thread-1, 时间:Fri Jan 11 16:31:52 2019
线程:Thread-2, 时间:Fri Jan 11 16:31:53 2019
线程:Thread-1, 时间:Fri Jan 11 16:31:53 2019
线程:Thread-2, 时间:Fri Jan 11 16:31:56 2019
线程:Thread-2, 时间:Fri Jan 11 16:31:59 2019

多线程

多线程类似于同时执行多个不同程序,多线程优点:

(1)易于调度。

(2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。

(3)开销少。创建线程比创建进程要快,所需开销很少
线程模块

Python3中通过两个标准库_thread(python2中为thread)和threading提供对线程的支持。
_thread 提供低级别的、原始的线程以及一个简单的锁。

threading 提供包括_thread提供的方法以及其他方法:

threading.currentThread() 返回当前的线程变量

threading.enumerate ()返回一个包含正在运行的线程的list,正在运行指线程启动后、结束前,不包括启动前和终止后的线程

threading.activeCount() 返回正在运行的线程数量,
len(threading.enumerate())作用相同

线程模块同样提供Thread类处理线程,Thread提供的方法:
run() 用以表示线程活动的方法

start() 启动线程

join(timeout) 阻塞调用,直到join()被中止、正常退出、抛出异常或是超时

isAlive() 判断线程是否活动 这里isAlive()=is_alive()

getName() 获取线程名

setName() 设置线程名

# threading 多线程 例子
import threading
import time


def about():
    print('我是多线程,我就是快')
    time.sleep(0.5)
    
if __name__=='__main__':
    for i in range(5):
        # 创建新的线程
        t = threading.Thread(target=about) # 这里about不要加()
        t.start() # 启动线程
        # 快的不要不要的
我是多线程,我就是快
我是多线程,我就是快
我是多线程,我就是快
我是多线程,我就是快
我是多线程,我就是快
# 主线程会等待所有子线程结束后才结束
import threading

from time import sleep, ctime


def sing():
    for i in range(3):
        print('singing', i)
        sleep(1)
        
def dance():
    for i in range(3):
        print('dancing', i)
        sleep(1)
        
if __name__=='__main__':
    print('start'.center(30, '*'))
    
    # 创建线程
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    
    # 启动线程
    t1.start() 
    t2.start()
    
    sleep(6) # 不写此行,会出现歌舞未完晚会先完的笑话
    
    print('end'.center(30, '*'))
************start*************
singing 0
dancing 0
singing 1
dancing 1
singing 2
dancing 2
*************end**************
查看线程数目
# 查看线程数目例子
#!/usr/bin/python3
# -*- coding:utf-8 -*-
import threading

from time import sleep, ctime


def sing():
    for i in range(3):
        print('singing', i)
        sleep(1)
        
def dance():
    for i in range(3):
        print('dancing', i)
        sleep(1)
        
if __name__=='__main__':
    print('start'.center(30, '*'), ctime())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=sing)
    
    t1.start()
    t2.start()
    
    while threading.activeCount()>1:
        print('当前线程数为:', threading.activeCount())
        sleep(0.5)
    print('end'.center(30, '*'), ctime())
    '''这里jupyter notebook运行此处代码会出现死循环,但是在linux下无此问题,
    所以这里不运行代码,用Linux截图显示'''
************start************* Tue Mar 26 10:16:08 2019
singing 0
singing当前线程数为: 7
 0
当前线程数为: 7
singing 1
singing 1
当前线程数为: 7
当前线程数为: 7



---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

<ipython-input-9-b8fdd2db5e43> in <module>
     27     while threading.activeCount()>1:
     28         print('当前线程数为:', threading.activeCount())
---> 29         sleep(0.5)
     30     print('end'.center(30, '*'), ctime())
     31     '''这里jupyter notebook运行此处代码会出现死循环,但是在linux下无此问题,


KeyboardInterrupt: 
多线程显示正在运行的线程数
threading 注意点
'''
线程执行代码的封装:
    在使用threading模块时,定义一个新的子类,继承threading.Thread,
    然后重写run方法
'''
# 线程执行代码的封装 例子
import threading

import time


class MyThread(threading.Thread): # 继承threading.Thread
    def run(self): # 重写run()
        for i in range(3):
            time.sleep(1)
            msg = '当前线程为:'+self.name+'@'+str(i)
            print(msg)
            
if __name__=='__main__':
    mt = MyThread()
    mt.start()
当前线程为:Thread-7@0
当前线程为:Thread-7@1
当前线程为:Thread-7@2
'''
例子说明:

python的threading.Thread类有一个run()方法,用于定义线程的功能函数,

可在自己的线程类中覆盖该方法,在创建自己的线程实例后,
python会调用用户自定义的run()
'''
多线程共享全局变量

在一个进程内的所有线程共享全局变量,能够在不适用其他方式的前提下完成多线程之间的数据共享(这点要比多进程要好)

缺点就是,线程是对全局变量随意修改可能造成多线程之间对全局变量的混乱(即线程非安全)

# 多线程共享全局变量 例子
from threading import Thread

import time


g_num = 50

def work1():
    global g_num
    
    for i in range(5):
        g_num +=2
        print('\nin work1, g_num = {}'.format(g_num))

def work2():
    global g_num
    
    for i in range(3):
        g_num += 3
        print('\nin work2, g_num = {}'.format(g_num))
        
if __name__=='__main__':
    t1 = Thread(target=work1)
    t2 = Thread(target=work2)
    
    t1.start()
    t2.start()
    # 这里会出现全局变量混乱现象
in work1, g_num =  52

in work1, g_num =  54

in work1, g_num =  56

in work1, g_num =  58

in work1, g_num =  60

in work2, g_num =  63

in work2, g_num =  66

in work2, g_num =  69
多线程全局变量数据混乱
列表当实参传递到线程中
# 例子
from threading import Thread

import time


def work1(nums):
    nums.append(44)
    print('in work1'.center(30, "*"), nums)
    
def work2(nums):
    # 延时,保证t1线程执行完毕
    time.sleep(1)
    print('in work2'.center(30, "*"), nums)
    
g_nums = [11, 22, 33]

t1 = Thread(target=work1, args=(g_nums, ))
t1.start()

t2 = Thread(target=work2, args=(g_nums, ))
t2.start()
***********in work1*********** [11, 22, 33, 44]
***********in work2*********** [11, 22, 33, 44]
进程VS线程

进程:进程是系统进行资源分配和调度的一个独立单位.

线程线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

进程可以理解为一台电脑上可以同时运行多个QQ
线程可以理解为一个QQ可以开多个聊天窗口

进程和线程的关系:

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

(3)处理机分给线程,即真正在处理机上运行的是线程

(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.

进程与线程的区别:

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

各自优缺点

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

线程同步

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。

# 线程不安全 例子
from threading import Thread

import time


g_num = 0
g_number = 0

def test1():
    global g_num
    
    for i in range(1000000):
        g_num +=1
    print('in test1, g_num = {}'.format(g_num))
        
def test2():
    global g_num
    
    for i in range(1000000):
        g_num +=1
    print('***in test2, g_num = {}'.format(g_num))
        
def test3():
    global g_number
    
    for i in range(1000000):
        g_number +=1
    print('***in test3, g_number = {}'.format(g_number))
        
def test4():
    global g_number
    
    for i in range(1000000):
        g_number +=1
    print('***in test4, g_number = {}'.format(g_number))
        
# 未对线程运行进行调节
t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()

print('g_num = {}'.format(g_num))

time.sleep(5) # 保证线程t1、t2执行完毕

# 对线程运行进行调节
t3 = Thread(target=test3)
t3.start()

time.sleep(5) # 睡眠5s,保证线程t3可以完全执行完毕

t4 = Thread(target=test4)
t4.start()
t4.join()# 线程阻塞
print('g_number = {}'.format(g_number))
g_num = 362029
in test1, g_num = 1057030
***in test2, g_num = 1584212
***in test3, g_number = 1000000
***in test4, g_number = 2000000
g_number = 2000000
互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定。

添加锁的原则是:在保证程序正确的前提下,尽可能的少锁住代码。(这样锁死的时间就越少)

threading模块中的Lock类,可以方便的处理锁定:

创建锁: mutex = threading.Lock()

锁定:mutex.acquire([blocking])

blocking参数解释:

若设定blocking为True,则当前线程堵塞,直到获得这个锁为止(默认为True)

若blocking设置为False,则当前线程不会堵塞

释放:mutex.release()

# 用互斥锁实现上例的进程同步 例子
from threading import Thread, Lock

from time import sleep


g_num = 0

def test1():
    global g_num
    
    for i in range(100000):
        # True表示阻塞,即若这个锁在要上锁之前已经被上锁,那么此线程会一直等待到解锁
        # False表示非阻塞,即不管本次上锁能否成功,都不会卡在这里,会继续执行下面的代码
        mutexFlag = mutex.acquire(True)
        
        if mutexFlag:
            g_num +=1
            mutex.release()
    print('in test1, g_num={}'.format(g_num))
    
def test2():
    global g_num
    
    for i in range(100000):
        # True表示阻塞,即若这个锁在要上锁之前已经被上锁,那么此线程会一直等待到解锁
        # False表示非阻塞,即不管本次上锁能否成功,都不会卡在这里,会继续执行下面的代码
        mutexFlag = mutex.acquire(True)
        
        if mutexFlag:
            g_num +=1
            mutex.release()
    print('**in test2, g_num={}'.format(g_num))
    
# 创建一个互斥锁,这个锁默认是未上锁状态
mutex = Lock()

t1 = Thread(target=test1)
t1.start()

t2 = Thread(target=test2)
t2.start()

print('for total g_num = {}'.format(g_num))
for total g_num = 11476
in test1, g_num=190843
**in test2, g_num=200000
'''
上锁解锁过程

当一个线程调用Lock的acquire()获得锁时,锁就进入“locked’状态

每次只有一个线程可以获得锁,如果此时另一个线程试图获得此锁,
尝试获取锁线程会变为”blocked“状态,称为”阻塞“
直到拥有锁的线程调用锁的release()方法释放锁,锁进入”unlocked“状态

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,
并使得该线程进入运行(running)状态。
'''

'''
锁的好处:
    确保了某段关键代码只能由一个线程从头到尾完整地执行
    
锁的坏处:
    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,
    效率就大大地下降了
    
    由于可以存在多个锁,不同的线程持有不同的锁,
    并试图获取对方持有的锁时,可能会造成死锁
'''

'''
对多线程-非共享数据(非全局变量):
    在多线程开发中,全局变量是多个线程都共享的数据,
    而局部变量等是各自线程的,是非共享的
'''
死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。


死锁图解

避免死锁
程序设计时要尽量避免(银行家算法)
添加超时时间等

银行家算法

一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。

例如:有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态如图所示。

银行家算法问题图

对于a图的状态,按照安全序列的要求,我们选的第一个客户应满足该客户所需的贷款小于等于银行家当前所剩余的钱款,可以看出只有C2客户能被满足:C2客户需1个资金单位,小银行家手中的2个资金单位,于是银行家把1个资金单位借给C2客户,使之完成工作并归还所借的3个资金单位的钱,进入b图。同理,银行家把4个资金单位借给C3客户,使其完成工作,在c图中,只剩一个客户C1,它需7个资金单位,这时银行家有8个资金单位,所以C1也能顺利借到钱并完成工作。最后(见图d)银行家收回全部10个资金单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银行家才是安全的。否则的话,若在图b状态时,银行家把手中的4个资金单位借给了C1,则出现不安全状态:这时C1,C3均不能完成工作,而银行家手中又没有钱了,系统陷入僵持局面,银行家也不能收回投资。

综上所述,银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。

'''同步应用'''
# 使用互斥锁完成多个任务,有序的进行工作
import queue
from threading import Thread, Lock

from time import sleep

class Task1(Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print('Task 1'.center(30, "*"))
                q.put(1)
                if q.full():
                    break
                sleep(0.5)
                lock2.release()
                
                
            
class Task2(Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print('Task 2'.center(30, "*"))
                q.put(1)
                if q.full():
                    break
                sleep(0.5)
                lock3.release()
                
        
class Task3(Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print('Task 3'.center(30, "*"))
                q.put(1)
                if q.full():
                    break
                sleep(0.5)
                lock1.release()
  
q = queue.Queue(9) # 消息队列,控制线程循环
# 使用Lock创建出的锁默认不上锁
lock1 = Lock()
# 创建另一把锁,并“锁上“
lock2 = Lock()
lock2.acquire()
# 创建另一把锁,并“锁上“
lock3 = Lock()
lock3.acquire()

t1 = Task1()
t2 = Task2()
t3 = Task3()

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

if q.full():
    t1.terminate()
    t2.terminate()
    t3.terminate()
************Task 1************
************Task 2************
************Task 3************
************Task 1************
************Task 2************
************Task 3************
************Task 1************
************Task 2************
************Task 3************
生产者消费者模式

队列

先进先出

后进先出

线程优先级队列( Queue):

Python 的 Queue 模块中提供了同步的、线程安全的队列类,

包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue

这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),
能够在多线程中直接使用。可以使用队列来实现线程间的同步。

import queue

q = queue.Queue()
这里的Queue和进程里Queue的方法和属性功能相同,不再赘述
Queue.task_done()
在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
'''

# 用FIFO队列实现生成者消费者问题 例子:
'''
例子没有结束控制,会一直循环,不建议直接运行
'''
import threading

import time

from queue import Queue
# 在py2中导入Queue方法:
# from Queue import Queue

class Producer(threading.Thread):
    
    def run(self):
        global queue
        count = 0
        
        while True:
            if(queue.qsize()<1000):
                for i in range(100):
                    count +=1
                    msg = '生成产品' + str(count)
                    queue.put(msg)
                    print(msg)
                    
            time.sleep(0.5)
            

class Consumer(threading.Thread):
    def run(self):
        global queue
        
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name + '消费了' + queue.get()
                    print(msg)
                
            time.sleep(1)
            
            
if __name__=='__main__':
    queue=Queue()
    
    for i in range(500):
        queue.put('初始产品'+str(i))
        
    for i in range(2):
        p = Producer()
        p.start()
                    
    for i in range(5):
        c = Consumer()
        c.start()
                    
生成产品1
生成产品2
生成产品3
生成产品4
生成产品5
生成产品6
生成产品7
生成产品8
生成产品9
生成产品10
生成产品11
生成产品12
生成产品13
生成产品14
生成产品15
生成产品16
生成产品17
生成产品18
生成产品19
生成产品20
生成产品21
生成产品22
生成产品23
生成产品24
生成产品25
生成产品26
生成产品27
生成产品28
生成产品29
生成产品30
生成产品31
生成产品32
生成产品33
生成产品34
生成产品35
生成产品36
生成产品37
生成产品38
生成产品39
生成产品40
生成产品41
生成产品42
生成产品43
生成产品44
生成产品45
生成产品46
生成产品47
生成产品48
生成产品49
生成产品50
生成产品51
生成产品52
生成产品53
生成产品54
生成产品55
生成产品56
生成产品57
生成产品58
生成产品59
生成产品60
生成产品61
生成产品62
生成产品63
生成产品64
生成产品65
生成产品66
生成产品67
生成产品68
生成产品69
生成产品70
生成产品71
生成产品72
生成产品73
生成产品74
生成产品75
生成产品76
生成产品77
生成产品78
生成产品79
生成产品80
生成产品81
生成产品82
生成产品83
生成产品84
生成产品85
生成产品86
生成产品87
生成产品88
生成产品89
生成产品90
生成产品91
生成产品92
生成产品93
生成产品94
生成产品95
生成产品96
生成产品97
生成产品98
生成产品99
生成产品100
生成产品1
生成产品2
生成产品3
生成产品4
生成产品5
生成产品6
生成产品7
生成产品8
生成产品9
生成产品10
生成产品11
生成产品12
生成产品13
生成产品14
生成产品15
生成产品16
生成产品17
生成产品18
生成产品19
生成产品20
生成产品21
生成产品22
生成产品23
生成产品24
生成产品25
Thread-6消费了初始产品0
Thread-6消费了初始产品1
Thread-6消费了初始产品2
生成产品26
生成产品27
生成产品28
生成产品29
生成产品30
生成产品31
生成产品32
生成产品33
生成产品34
生成产品35
生成产品36Thread-7消费了初始产品3
Thread-7消费了初始产品4
Thread-7消费了初始产品5

Thread-8消费了初始产品6
Thread-8消费了初始产品7
Thread-8消费了初始产品8
生成产品37
生成产品38
生成产品39
生成产品40
生成产品41
Thread-9消费了初始产品9
Thread-9消费了初始产品10
Thread-9消费了初始产品11
Thread-10消费了初始产品12
Thread-10消费了初始产品13
Thread-10消费了初始产品14
生成产品42
生成产品43
生成产品44
生成产品45
生成产品46
生成产品47
生成产品48
生成产品49
生成产品50
生成产品51
生成产品52
生成产品53
生成产品54
生成产品55
生成产品56
生成产品57
生成产品58
生成产品59
生成产品60
生成产品61
生成产品62
生成产品63
生成产品64
生成产品65
生成产品66
生成产品67
生成产品68
生成产品69
生成产品70
生成产品71
生成产品72
生成产品73
生成产品74
生成产品75
生成产品76
生成产品77
生成产品78
生成产品79
生成产品80
生成产品81
生成产品82
生成产品83
生成产品84
生成产品85
生成产品86
生成产品87
生成产品88
生成产品89
生成产品90
生成产品91
生成产品92
生成产品93
生成产品94
生成产品95
生成产品96
生成产品97
生成产品98
生成产品99
生成产品100
ThreadLocal

多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

'''
使用函数传参的方法
每层都要传入参数,啰嗦的很
'''
def process_student(name):
    std = Student(name)
    # std是局部变量,但是每个函数都要用它,因此必须传进去:
    do_task_1(std)
    do_task_2(std)

def do_task_1(std):
    do_subtask_1(std)
    do_subtask_2(std)

def do_task_2(std):
    do_subtask_2(std)
    do_subtask_2(std)
    
'''
使用全局字典的方法
这种方式理论上是可行的,
它最大的优点是消除了std对象在每层函数中的传递问题,
但是,每个函数获取std的代码有点low。
'''
global_dict = {}

def std_thread(name):
    std = Student(name)
    # 把std放到全局变量global_dict中:
    global_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():
    # 不传入std,而是根据当前线程查找:
    std = global_dict[threading.current_thread()]
    ...

def do_task_2():
    # 任何函数都可以查找出当前线程的std变量:
    std = global_dict[threading.current_thread()]
    ...
使用ThreadLocal

threadlocal是用于解决多线程之间的共享数据的参数紊乱问题

import threading

# 用threading.local() 生成全局变量
global_var = threading.local()

ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题

# ThreadLocal 例子
import threading

# 创建全局ThreadLocal对象
local_school = threading.local()

def process_student():
    # 获取当前线程相关的student
    std = local_school.student
    print('hello,{}(in {})'.format(std, threading.current_thread().name))
    
def process_thread(name):
    # 绑定ThreadLocal的student
    local_school.student = name
    process_student()
    
t1 = threading.Thread(target=process_thread, args=('zhangsan', ), name='Thread_A')
t2 = threading.Thread(target=process_thread, args=('lisi', ), name='Thread_B')

t1.start()
t2.start()

t1.join()
t2.join()

# 全局变量local_school就是一个ThreadLocal对象,
# 每个Thread对它都可以读写student属性,但互不影响。
# 你可以把local_school看成全局变量,
# 但每个属性如local_school.student都是线程的局部变量,
# 可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

# 可以理解为全局变量local_school是一个dict,
# 不但可以用local_school.student,还可以绑定其他变量,
# 如local_school.teacher等等。

hello,zhangsan(in Thread_A)
hello,lisi(in Thread_B)
异步

老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

# 异步例子
from multiprocessing import Pool

import time

import os


def test_1():
    print('进程池中的进程:pid = {}, ppid = {}'.format(os.getpid(), os.getppid()))
    for i in range(3):
        print('{}'.format(i).center(10, '*'))
        time.sleep(0.5)
        
    return 'function test_1'

def test_2(args):
    print('callback func pid = {}'.format(os.getpid()))
    print('callback func args = {}'.format(args))
    
pool = Pool(3)
pool.apply_async(func=test_1, callback=test_2)

time.sleep(3)

print('主进程:pid = {}'.format(os.getpid()))
进程池中的进程:pid = 3912, ppid = 3897
****0*****
****1*****
****2*****
callback func pid = 3897
callback func args = function test_1
主进程:pid = 3897
GIL(全局解释锁)

这里特别注意:在python中多进程比多线程的效率要高

#加载动态库
from ctypes import *

lib = cdll.LoadLibrary("loadName") #这里是用于导入库
分布式进程

参考网址:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431929340191970154d52b9d484b88a7b343708fcc60000

在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

原有的Queue可以继续使用,但是,通过managers模块把Queue通过网络暴露出去,就可以让其他机器的进程访问Queue了。

# task_master.py

import random, time, queue
from multiprocessing.managers import BaseManager

# 发送任务的队列:
task_queue = queue.Queue()
# 接收结果的队列:
result_queue = queue.Queue()

# 从BaseManager继承的QueueManager:
class QueueManager(BaseManager):
    pass

# 把两个Queue都注册到网络上, callable参数关联了Queue对象:
QueueManager.register('get_task_queue', callable=lambda: task_queue)
QueueManager.register('get_result_queue', callable=lambda: result_queue)
# 绑定端口5000, 设置验证码'abc':
manager = QueueManager(address=('', 5000), authkey=b'abc')
# 启动Queue:
manager.start()
# 获得通过网络访问的Queue对象:
task = manager.get_task_queue()
result = manager.get_result_queue()
# 放几个任务进去:
for i in range(10):
    n = random.randint(0, 10000)
    print('Put task %d...' % n)
    task.put(n)
# 从result队列读取结果:
print('Try get results...')
for i in range(10):
    r = result.get(timeout=10)
    print('Result: %s' % r)
# 关闭:
manager.shutdown()
print('master exit.')

# 当我们在一台机器上写多进程程序时,创建的Queue可以直接拿来用,
# 但是,在分布式多进程环境下,
# 添加任务到Queue不可以直接对原始的task_queue进行操作,
# 那样就绕过了QueueManager的封装,
# 必须通过manager.get_task_queue()获得的Queue接口添加。


# task_worker.py

import time, sys, queue
from multiprocessing.managers import BaseManager

# 创建类似的QueueManager:
class QueueManager(BaseManager):
    pass

# 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字:
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue')

# 连接到服务器,也就是运行task_master.py的机器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
# 端口和验证码注意保持与task_master.py设置的完全一致:
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
# 从网络连接:
m.connect()
# 获取Queue的对象:
task = m.get_task_queue()
result = m.get_result_queue()
# 从task队列取任务,并把结果写入result队列:
for i in range(10):
    try:
        n = task.get(timeout=1)
        print('run task %d * %d...' % (n, n))
        r = '%d * %d = %d' % (n, n, n*n)
        time.sleep(1)
        result.put(r)
    except Queue.Empty:
        print('task queue is empty.')
# 处理结束:
print('worker exit.')

异步IO

为解决CPU高速执行能力和IO设备的龟速严重不匹配,提供了多线程和多进程方法,但是还有一种解决方法:异步IO。(当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。)

异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程:

loop = get_event_loop()
while True:
event = loop.get_event()
process_event(event)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容