1. 字典根据键从小到大排序?
In[38]: dic = {"name": "Tom", "age": 30, "country": "USA"}
In[39]: sorted(dic.iteritems(), key=lambda x : x[0])
Out[39]: [('age', 30), ('country', 'USA'), ('name', 'Tom')]
In[40]: dict(sorted(dic.iteritems(), key=lambda x : x[0]))
Out[40]: {'age': 30, 'country': 'USA', 'name': 'Tom'}
sorted
语法:
sorted(iterable[, cmp[, key[, reverse]]])
参数说明:
iterable
:可迭代对象。cmp
:比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0。key
:主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。reverse
:排序规则,reverse = True 降序 , reverse = False 升序(默认)。
2. 单例模式
class Single(object):
__isstance = None
__first_init = False
def __new__(cls, *args, **kwargs):
if not cls.__isstance:
cls.__isstance = object.__new__(cls)
return cls.__isstance
def __init__(self, name):
if not self.__first_init:
self.name = name
Singleton.__first_init = True
3. 统计字符串每个单词出现的次数
In[41]: from collections import Counter
In[42]: s3 = "kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"
In[43]: Counter(s3)
Out[43]:
Counter({';': 6,
'a': 4,
'b': 1,
'd': 3,
'f': 5,
'g': 1,
'h': 6,
'j': 3,
'k': 1,
'l': 9,
's': 2})
4. a=(1,)b=(1),c=(“1”) 分别是什么类型的数据?
In[46]: a = (1,)
In[47]: type(a)
Out[47]: tuple
In[48]: b = (1)
In[49]: type(b)
Out[49]: int
In[50]: c = ("1")
In[51]: type(c)
Out[51]: str
5. x=“abc”,y=“def”,z=[“d”,“e”,“f”],分别求出 x.join(y) 和 x.join(z) 返回的结果
join() 括号里面的是可迭代对象,x 插入可迭代对象中间,形成字符串,结果一致
In [1]: x = "abc"
In [2]: y = "def"
In [3]: z = ["d", "e", "f"]
In [4]: x.join(y)
Out[4]: 'dabceabcf'
In [5]: x.join(z)
Out[5]: 'dabceabcf'
6. 请说明 sort 和 sorted 对列表排序的区别
sort()
与sorted()
的不同在于,sort
是在原位重新排列列表,而sorted()
是产生一个新的列表。sorted(L)
返回一个排序后的 L,不改变原始的L;L.sort()
是对原始的 L 进行操作,调用后原始的 L 会改变,没有返回值;所以a = a.sort()是错的啦!a = sorted(a)才对。sorted()
适用于任何可迭代容器,list.sort() 仅支持 list- 基于以上两点,
sorted
使用频率比list.sort()
更高些,所以 Python 中更高级的排序技巧便通过 sorted() 来演示
7. 对 foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4] 进行排序,使用 lambda 函数从小到大排序
In [6]: foo = [-5,8,0,4,9,-4,-20,-2,8,2,-4]
In [7]: sorted(foo, key=lambda x: x)
Out[7]: [-20, -5, -4, -4, -2, 0, 2, 4, 8, 8, 9]
In [9]: sorted(foo, key=lambda x: x, reverse=True)
Out[9]: [9, 8, 8, 4, 2, 0, -2, -4, -4, -5, -20]
8. int(“1.4”)、int(1.4)的输出结果?
In [10]: int("1.5")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-10-8d7b10d14577> in <module>()
----> 1 int("1.5")
ValueError: invalid literal for int() with base 10: '1.5'
In [11]: eval("1.5")
Out[11]: 1.5
In [12]: int(1.5)
Out[12]: 1
9. Python 垃圾回收机制?
1.引用计数
In [13]: import sys
...: s = "hello world"
...: sys.getrefcount(s)
...:
Out[13]: 3
In [14]: s = "china"
In [15]: sys.getrefcount(s)
Out[15]: 2
In [16]: s = "beijing"
In [17]: sys.getrefcount(s)
Out[17]: 2
2.分代技术
Python默认定义了三代对象集合,索引数越大,对象存活时间越长
Python中使用了某些启发式算法(heuristics)来加速垃圾回收。例如,越晚创建的对象更有可能被回收。对象被创建之后,垃圾回收器会分配它们所属的代(generation)。每个对象都会被分配一个代,而被分配更年轻代的对象是优先被处理的。
3.引用循环
垃圾回收器会定时寻找这个循环,并将其回收。举个例子,假设有两个对象o1和o2,而且符合o1.x == o2和o2.x == o1这两个条件。如果o1和o2没有其他代码引
10. 什么是 lambda 函数?有什么好处?
lambda
函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的匿名函数
1、lambda
函数比较轻便,即用即删除,很适合需要完成一项功能,但是此功能只在此一处使用,
连名字都很随意的情况下;
2、匿名函数,一般用来给 filter
, map
这样的函数式编程服务;
3、作为回调函数,传递给某些应用,比如消息处理
11. 简述 read、readline、readlines 的区别?
read
读取整个文件
readline
读取下一行,使用生成器方法
readlines
读取整个文件到一个迭代器以供我们遍历
12. Python 中单下划线和双下划线使用
__foo__
一种约定,Python 内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如 __init__()
, __del__()
, __call__()
这些特殊方法
_foo
:一种约定,用来指定变量私有。程序员用来指定私有变量的一种方式。不能用 from module import *
导入,其他方面和公有一样访问;
__foo
:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问.
12. 代码描述静态方法 (staticmethod),类方法(classmethod) 和实例方法
@staticmethod 和@classmethod 都可以直接类名.方法名()来调用,不用在实例化一个类。
class A(object):
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()
@staticmethod 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法
IND = 'ON'
class Kls(object):
def __init__(self, data):
self.data = data
@staticmethod
def check_ind():
return (IND == 'ON')
def do_reset(self):
if self.check_ind():
print('Reset done for:', self.data)
def set_db(self):
if self.check_ind():
self.db = 'New db connection'
print('DB connection made for: ', self.data)
ik1 = Kls(12)
ik1.do_reset()
ik1.set_db()
13. 新式类和旧式类的区别?
- Python 2.x 中默认都是经典类,只有显式继承了 object 才是新式类,Python 3.x 中默认都是新式类,经典类被移除,不必显式的继承 object。
- 新式类都从 object 继承,经典类不需要。
- 新式类的 MRO(method resolution order 基类搜索顺序)算法采用 C3 算法广度优先搜索,而旧式类的 MRO 算法是采用深度优先搜索。
- 新式类相同父类只执行一次构造函数,经典类重复执行多次。
14. 请写出一个在函数执行后输出日志的装饰器
装饰器就是一个函数,它可以在不需要做任何代码变动的前提下给一个函数增加额外功能,启动装饰的效果。 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。 下面是一个日志功能的装饰器
from functools import wraps
def do_log(func):
@wraps(func)
def wrapper(*args, **kw):
if func.__name__ == "debug":
msg = "debug {}".format(args[0])
elif func.__name__ == "info":
msg = "info {}".format(args[0])
else:
msg = "unknown {}".format(args[0])
return func(msg, **kw)
return wrapper
@do_log
def debug(msg):
print(msg)
@do_log
def info(msg):
print(msg)
if __name__ == "__main__":
debug("123")
info("abc")
15. 请解释一下协程的优点
子程序切换不是线程切换,而是由程序自身控制
没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁
16. a=“hello” 和 b=“你好” 编码成 bytes 类型
In [1]: a = b"hello"
In [2]: b = bytes("hello", "utf-8")
In [3]: c = "北京".encode("utf-8")
In [4]: a, b, c
Out[4]: (b'hello', b'hello', b'\xe5\x8c\x97\xe4\xba\xac')
17. 写一个函数,接收整数参数 n,返回一个函数,函数的功能是把函数的参数和 n 相乘并把结果返回
主要考察闭包知识点:
1.必须有一个内嵌函数
2.内嵌函数必须引用外部函数中的变量
3.外部函数的返回值必须是内嵌函数
In [5]: def f(n):
...: def g(m):
...: return n*m
...: return g
...:
In [6]: f(3)(4)
Out[6]: 12
In [7]: m = f(4)
In [8]: m(5)
Out[8]: 20
18. Python 字典和 json 字符串相互转化方法
在 Python 中使用 dumps 方法 将 dict 对象转为 Json 对象,使用 loads 方法可以将 Json 对象转为 dict 对象。
In [9]: dic = {'a': 10, 'b': "123", 'c': "hello world"}
In [10]: import json
In [11]: json_str = json.dumps(dic)
In [12]: json_str
Out[12]: '{"a": 10, "b": "123", "c": "hello world"}'
In [13]: dic_loads = json.loads(json_str)
In [14]: dic_loads
Out[14]: {'a': 10, 'b': '123', 'c': 'hello world'}
In [15]: json.loads(str(dic))
---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent
JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
In [16]: json.loads(str(dic).replace("'", "\""))
Out[16]: {'a': 10, 'b': '123', 'c': 'hello world'}
如果直接使用 json.loads(str(dic))会报错原因就是,单引号的字符串不符合 Json 的标准格式所以再次使用了 replace(“'”, “”")。
Json 的标准格式是不支持单引号型字符串的。
19. 分布式锁
分布式锁是控制分布式系统之间的同步访问共享资源的一种方式。 对于分布式锁的目标,我们必须首先明确三点:
- 任何一个时间点必须只能够有一个客户端拥有锁。
- 不能够有死锁,也就是最终客户端都能够获得锁,尽管可能会经历失败。
- 错误容忍性要好,只要有大部分的Redis实例存活,客户端就应该能够获得锁。 分布式锁的条件 互斥性:分布式锁需要保证在不同节点的不同线程的互斥 可重入性:同一个节点上的同一个线程如果获取了锁之后,能够再次获取这个锁。 锁超时:支持超时释放锁,防止死锁 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。 支持阻塞和非阻塞:可以实现超时获取失败,tryLock(long timeOut) 支持公平锁和非公平锁
分布式锁的实现方案 1、数据库实现(乐观锁) 2、基于zookeeper的实现 3、基于Redis的实现(推荐)
20. Python 实现分布式锁
REDIS分布式锁实现的方式:SETNX + GETSET,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。 多个进程执行以下Redis命令:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX 返回1,说明该进程获得锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁的有效时间)。 如果 SETNX 返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。
import time
import redis
from conf.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
class RedisLock:
def __init__(self):
self.conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=1)
self._lock = 0
self.lock_key = ""
@staticmethod
def my_float(timestamp):
"""
Args:
timestamp:
Returns:
float或者0
如果取出的是None,说明原本锁并没人用,getset已经写入,返回0,可以继续操作。
"""
if timestamp:
return float(timestamp)
else:
#防止取出的值为None,转换float报错
return 0
@staticmethod
def get_lock(cls, key, timeout=10):
cls.lock_key = f"{key}_dynamic_lock"
while cls._lock != 1:
timestamp = time.time() + timeout + 1
cls._lock = cls.conn.setnx(cls.lock_key, timestamp)
# if 条件中,可能在运行到or之后被释放,也可能在and之后被释放
# 将导致 get到一个None,float失败。
if cls._lock == 1 or (
time.time() > cls.my_float(cls.conn.get(cls.lock_key)) and
time.time() > cls.my_float(cls.conn.getset(cls.lock_key, timestamp))):
break
else:
time.sleep(0.3)
@staticmethod
def release(cls):
if cls.conn.get(cls.lock_key) and time.time() < cls.conn.get(cls.lock_key):
cls.conn.delete(cls.lock_key)
def redis_lock_deco(cls):
def _deco(func):
def __deco(*args, **kwargs):
cls.get_lock(cls, args[1])
try:
return func(*args, **kwargs)
finally:
cls.release(cls)
return __deco
return _deco
@redis_lock_deco(RedisLock())
def my_func():
print("myfunc() called.")
time.sleep(20)
if __name__ == "__main__":
my_func()
21. 魔法函数 _call_怎么使用?
_call_ 可以把类实例当做函数调用。 使用示例如下
class Bar:
def __call__(self, *args, **kwargs):
print('in call')
if __name__ == '__main__':
b = Bar()
b()
22. Python 中的反射了解么?
Python 的反射机制设定较为简单,一共有四个关键函数分别是 getattr、hasattr、setattr、delattr
hasattr()、getattr()、setattr() 的用法:
这三个方法属于 Python 的反射机制里面的,
- hasattr 可以判断一个对象是否含有某个属性,
- getattr 可以充当 get 获取对象属性的作用。
- setattr 可以充当 person.name = "liming"的赋值操作。
代码示例如下:
class Person():
def __init__(self):
self.name = "liming"
self.age = 12
def show(self):
print(self.name)
print(self.age)
def set_name(self):
setattr(Person, "sex", "男")
def get_name(self):
print(getattr(self, "name"))
print(getattr(self, "age"))
print(getattr(self, "sex"))
def run():
if hasattr(Person, "show"):
print("判断 Person 类是否含有 show 方法")
Person().set_name()
Person().get_name()
if __name__ == '__main__':
run()
23. Python 的魔法方法及用途
1 _init_():
类的初始化方法。它获取任何传给构造器的参数(比如我们调用 x = SomeClass(10, ‘foo’) , __init__就会接到参数 10 和 ‘foo’ 。 __init__在 Python 的类定义中用的最多。
2 _new_:
__new__是对象实例化时第一个调用的方法,它只取下 cls 参数,并把其他参数传给 init 。 __new__很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候.
3 _del_:
__new__和 __init__是对象的构造器, __del__是对象的销毁器。它并非实现了语句 del x (因此该语句不等同于 x.del())。而是定义了当对象被垃圾回收时的行为。 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 socket 对象、文件对象。但是需要注意的是,当 Python 解释器退出但对象仍然存活的时候,__del__并不会 执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。
24. metaclass 作用?以及应用场景?
metaclass 即元类,metaclass 是类似创建类的模板,所有的类都是通过他来 create 的(调用new),这使得你可以自由的控制创建类的那个过程,实现你所需要的功能。 我们可以使用元类创建单例模式和实现 ORM 模式。
可以使用元类实现一个单例模式,代码如下
class Singleton(type):
def __init__(self, *args, **kwargs):
print("in __init__")
self.__instance = None
super(Singleton, self).__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
print("in __call__")
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kwargs)
return self.__instance
class Foo(metaclass=Singleton):
pass # 在代码执行到这里的时候,元类中的__new__方法和__init__方法其实已经被执行了,而不是在 Foo 实例化的时候执行。且仅会执行一次。
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
25. 在 Python 中是如何管理内存的?
- 垃圾回收:
Python 不像 C++,Java 等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对 Python 语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称 Python 语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值)。
- 引用计数:
Python 采用了类似 Windows 内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。当变量被绑定在一个对象上的时候,该变量的引用计数就是 1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为 0 的时候,该对就会被回收。
26. 当退出 Python 时是否释放所有内存分配?
不是的,循环引用其他对象或引用自全局命名空间的对象的模块,在 Python 退出时并非完全释放。
另外,也不会释放 c 库保留的内存部分。
27. Python 中递归的最大次数,那如何突破呢?
Python 有递归次数限制,默认最大次数为 2000。
In [17]: import sys
In [18]: sys.getrecursionlimit()
Out[18]: 2000
通过下面的代码可以突破这个限制
In [19]: sys.setrecursionlimit(2500)
In [20]: sys.getrecursionlimit()
Out[20]: 2500
另外需要注意的是 sys.setrecursionlimit() 只是修改解释器在解释时允许的最大递归次数,此外,限制最大递归次数的还和操作系统有关。
28. 一个包里有三个模块,demo1.py、demo2.py、demo3.py,但使用 from tools import * 导入模块时,如何保证只有 demo1、demo3 被导入了.
增加_init_.py 文件,并在文件中增加:
__all__ = ['demo1','demo3']
29. 代码中经常遇到的 *args, **kwargs 含义及用法
在函数定义中使用 *args 和 **kwargs 传递可变长参数。
*args 用来将参数打包成 tuple 给函数体调用。
**kwargs 打包关键字参数成 dict 给函数体调用。
29. Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?
特别说明以下划线开头的变量是有特殊意义的:
- 类变量若以单下划线(_)开头,代表不能直接被访问,类似于C#的受保护型变量(protected),表示不能通过import module_name而导入。
- 类变量若以双下划(__)开头,表示为类的私有成员,不能被导入和其他类变量访问。
- 以双下划开头和双下划线结尾的变量是 Python 里的专用标识,有特殊的身份。 如 Python 自定义类中都包括__init__和__add__方法,如果不重写__add__去执行两个类加法操作,程序会抛TypeError异常。只有重写后,才能正常执行加法操作。
“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量; “双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
以单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用“from xxx import *”而导入;以双下划线开头的(__foo)代表类的私有成员;
以双下划线开头和结尾的(_foo)代表 Python 里特殊方法专用的标识,如 _init()代表类的构造函数。
代码示例:
class Person:
"""docstring for ClassName"""
def __init__(self):
self.__age = 12
self._sex = 12
def _sex(self):
return "男"
def set_age(self,age):
self.__age = age
def get_age(self):
return self.__age
if __name__ == '__main__':
p=Person()
print(p._sex)
#print(p.__age)
#Python 自动将__age 解释成 _Person__age,于是我们用 _Person__age 访问,这次成功。
print(p._Person__age)
Python 变量命名习惯一般遵守蛇形命名法(snake case):
- 一般变量命名,book_id,book_store_count;
- 类名首字符为大写,如Python内置模块collections.abc中的Iterable类,我们自定义的Book类等;
- 类方法名:get_store_count();
- 其他特殊变量,全部大写M_PI,MAX_VEHICLE_SPEED
30. IO 多路复用的作用?
阻塞 I/O 只能阻塞一个 I/O 操作,而 I/O 复用模型能够阻塞多个 I/O 操作,所以才叫做多路复用。
I/O 多路复用是用于提升效率,单个进程可以同时监听多个网络连接 IO。 在 IO 密集型的系统中, 相对于线程切换的开销问题,IO 多路复用可以极大的提升系统效率。
1. 说一说多线程,多进程和协程的区别?
进程:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,
进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,
不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,
所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
线程:
线程是进程的一个实体,是 CPU 调度和分派的基本单位,
它是比进程更小的能独立运行的基本单位.
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),
但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
协程:
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,
直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
区别: 进程与线程比较: 线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,
而进程有自己独立的地址空间 - 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是
- 二者均可并发执行
- 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
协程与线程进行比较:
- 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样 Python 中则能使用多核 CPU。
- 线程进程都是同步机制,而协程则是异步
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
2. 什么是并发和并行?
"并行是指同一时刻同时做多件事情,而并发是指同一时间间隔内做多件事情”。
并发与并行是两个既相似而又不相同的概念:并发性,又称共行性,是指能处理多个同时性活动的能力;并行是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行,也亦是说并发事件之间不一定要同一时刻发生。
并发的实质是一个物理 CPU(也可以多个物理 CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。 并行性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同 CPU 上同时执行。
并行,是每个 CPU 运行一个程序。
3. 解释什么是异步非阻塞?
异步 异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
非阻塞 非阻塞是这样定义的,当线程遇到 I/O 操作时,不会以阻塞的方式等待 I/O 操作的完成或数据的返回,而只是将 I/O 请求发送给操作系统,继续执行下一条语句。当操作系统完成 I/O 操作时,以事件的形式通知执行 I/O 操作的线程,线程会在特定时候处理这个事件。简答理解就是如果程序不会卡住,可以继续执行,就是说非阻塞的。
4. threading.local 的作用?
threading.local()这个方法是用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,如果你在开发多线程应用的时候,需要每个线程保存一个单独的数据供当前线程操作,可以考虑使用这个方法,简单有效。代码示例
import threading
import time
a = threading.local()#全局对象
def worker():
a.x = 0
for i in range(200):
time.sleep(0.01)
a.x += 1
print(threading.current_thread(),a.x)
for i in range(20):
threading.Thread(target=worker).start()