# 轮训查询一个变量,判断是否停止, 如果线程卡在IO上面,将永远无法执行完毕
# 所以设置超时时间进行
# 超时装饰器
from threading import Thread
import sys
import time
def set_timeout(num, call_back):
def warp(function):
def warp2(*args, **kw):
class MyThread(Thread):
def __init__(self, _error = None,):
Thread.__init__(self)
self._error = _error
self.result = None
def run(self):
try:
self.result = function(*args, **kw)
except Exception as e :
self._error = str(e)
# def _stop(self):
# if self.is_alive():
# return
# ThreadStop()
t = MyThread("Timeout")
t.setDaemon(True)
t.start()
t.join(num) # 设置超时时间
# print("t._error:", t._error)
if isinstance(t._error, TimeoutError):
t._stop()
call_back()
raise TimeoutError('timeout for %s' % (repr(function)))
if t.isAlive():
t._stop()
call_back()
raise TimeoutError('timeout for %s' % (repr(function)))
if t._error is None:
return t.result
return warp2
return warp
# 超时回调函数
def callback2():
print("callback!")
@set_timeout(2, callback2) # 设置执行超时时间和回调函数
def run_func(*args, **kwargs):
time.sleep(1)
print(args)
# 线程
class Pooling():
def __init__(self):
self.stop = False
def run(self):
while not self.stop :
run_func(12, 332)
print("Pooling")
def stop2(self):
self.stop=True
c = Pooling()
t2 = Thread(target=c.run, args=())
t2.start()
t2.join()
event 对象最好单次使用,就是说,你创建一个 event 对象,让某个线程等待这个对象,一旦这个对象被设置为真,你就应该丢弃它。尽管可以通过 clear() 方法来重置 event 对象,但是很难确保安全地清理 event 对象并对它重新赋值。很可能会发生错过事件、死锁或者其他问题(特别是,你无法保证重置 event 对象的代码会在线程再次等待这个 event 对象之前执行)。如果一个线程需要不停地重复使用 event 对象,你最好使用 Condition 对象来代替。
from threading import Thread, Event
def run_func(*args, **kwargs):
time.sleep(1)
print(args)
event= Event()
class Pooling():
def __init__(self):
self.stop = False
def run(self):
event.wait()
while not self.stop :
run_func(12, 332)
print("Pooling")
def stop2(self):
self.stop=True
c = Pooling()
t2 = Thread(target=c.run, args=())
event.set()
t2.start()
c.stop2()
t2.join()
from threading import Thread, Event,Condition
import sys
import time
#Condition 对象实现了一个周期定时器,每当定时器超时的时候,其他线程都可以监测
class TimeNotification():
def __init__(self, ts):
self.ts = ts
self.cv = Condition()
self.flag =0
self.st = False
def start(self):
time_thread = Thread(target=self.run, args=())
time_thread.setDaemon(True)
time_thread.start()
def run(self):
while self.st == False:
time.sleep(self.ts)
with self.cv:
self.flag ^= 1
self.cv.notify_all()
def time_wait_for(self):
flag = self.flag
with self.cv:
while flag == self.flag:
print("wait")
self.cv.wait()
def stop(self):
self.st = True
def func(ticks, timer):
while ticks > 0:
timer.time_wait_for()
ticks = ticks - 1
print("ticking:", ticks)
timer = TimeNotification(3)
timer.start()
t1 = Thread(target=func, args=(10, timer))
t1.start()
t2 = Thread(target=func, args=(10, timer))
t2.start()
time.sleep(10)
timer.stop()
t1.join()
t2.join()
Lock 对象和 with 语句块一起使用可以保证互斥执行,就是每次只有一个线程可以执行 with 语句包含的代码块。with 语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。
from threading import Thread, Event,Condition, Lock
import sys
import time
#使用锁
# Lock 对象和 with 语句块一起使用可以保证互斥执行,就是每次只有一个线程可以执行 with
# 语句包含的代码块。with 语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。
class Data():
def __init__(self):
self.lock = Lock()
self.count =10
def desc(self):
with self.lock:
self.count = 0
time.sleep(4)
print(self.count)
def inc(self):
time.sleep(1)
with self.lock:
self.count = 1
print(self.count)
data = Data()
t1 = Thread(target=data.desc, args=())
t2 = Thread(target=data.inc, args=())
t1.start()
t2.start()
t1.join()
t2.join()
对锁的序号进行编码,按照顺序获取锁。这样即使不同的顺序获取锁,也不会造成死锁。
使用contextmanager
@contextmanager这个decorator接受一个generator,用yield语句把with … as var把变量输出出去,然后,with语句就可以正常地工作了:
@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')
with create_query('Bob') as q:
q.query()
#希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("%s>" % name)
with tag("h1"):
print("hello")
print("world")
上述代码执行结果为:
<h1>
hello
world
</h1>
import threading
from contextlib import contextmanager
# Thread-local state to stored information on locks already acquired
# 就是有这样一种变量,他是全局的,但是每个线程在访问的时候都会存储一份成为自己的局部变量,修改就不会相互影响了
# 每个线程可以把这个Manager复制一份成为自己的局部变量,自己可以随意修改,但是不会影响到其他线程,因为是复制的一份
# https://zhuanlan.zhihu.com/p/60126952
_local = threading.local()
@contextmanager
def acquire(*locks):
# Sort locks by object identifier
locks = sorted(locks, key=lambda x: id(x))
# Make sure lock order of previously acquired locks is not violated
acquired = getattr(_local,'acquired',[])
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# Acquire all of the locks
acquired.extend(locks)
_local.acquired = acquired
try:
for lock in locks:
lock.acquire()
yield
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del acquired[-len(locks):]
不论是单个锁还是多个锁中都使用 acquire() 函数来申请锁
import threading
x_lock = threading.Lock()
y_lock = threading.Lock()
def thread_1():
while True:
with acquire(x_lock, y_lock):
print('Thread-1')
def thread_2():
while True:
with acquire(y_lock, x_lock):
print('Thread-2')
t1 = threading.Thread(target=thread_1)
t1.daemon = True
t1.start()
t2 = threading.Thread(target=thread_2)
t2.daemon = True
t2.start()
简单的使用 concurrent.futures.ProcessPoolExecutor可以创建N个独立的Python解释器,利用CPU的多核进行密集的计算任务。
通过提供可选参数给 ProcessPoolExecutor(N) 来修改处理器数量。这个处理池会一直运行到with块中最后一个语句执行完成, 然后处理池被关闭。不过,程序会一直等待直到所有提交的工作被处理完成。
注意:
(1)被提交的任务必须是简单函数形式。对于方法、闭包和其他类型的并行执行还不支持
(2)函数参数和返回值必须兼容pickle,因为要使用到进程间的通信,所有解释器之间的交换数据必须被序列化
(3)在Unix上进程池通过调用 fork() 系统调用被创建,
它会克隆Python解释器,包括fork时的所有程序状态。 而在Windows上,克隆解释器时不会克隆状态。 实际的fork操作会在第一次调用 pool.map() 或 pool.submit() 后发生。
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
...
do work in parallel using pool
...
被提交到池中的工作必须被定义为一个函数。有两种方法去提交。 如果你想让一个列表推导或一个 map() 操作并行执行的话,可使用 pool.map()
# A function that performs a lot of work
from concurrent.futures import ProcessPoolExecutor
def work(x):
print(data, "work")
data = 1
with ProcessPoolExecutor() as pool:
pool.map(work, (data,data))
也可以使用 pool.submit() 来手动的提交单个任务。
def work(x):
...
return result
with ProcessPoolExecutor() as pool:
...
# 手动提交一个任务,结果是一个 Future 实例。 要获取最终结果,你需要调用它的 result() 方法
future_result = pool.submit(work, arg)
# Obtaining the result (blocks until done)
r = future_result.result()
...
如果不想阻塞,你还可以使用一个回调函数
def when_done(r):
print('Got:', r.result())
with ProcessPoolExecutor() as pool:
future_result = pool.submit(work, arg)
future_result.add_done_callback(when_done)
尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。 实际上,解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。
有两种策略来解决GIL的缺点。 首先,如果你完全工作于Python环境中,你可以使用 multiprocessing 模块来创建一个进程池, 并像协同处理器一样的使用它。例如,假如你有如下的线程代码:
from threading import Thread, Event,Condition, Lock
import sys
import time
import multiprocessing
pool = None
def work(args):
print("this is a work")
def works():
for i in range(10):
r = pool.apply(work, (i,))
# Initiaze the pool
if __name__ == '__main__':
import multiprocessing
pool = multiprocessing.Pool()
works()
这个通过使用一个技巧利用进程池解决了GIL的问题。 当一个线程想要执行CPU密集型工作时,会将任务发给进程池。 然后进程池会在另外一个进程中启动一个单独的Python解释器来工作。 当线程等待结果的时候会释放GIL。 并且,由于计算任务在单独解释器中执行,那么就不会受限于GIL了。 在一个多核系统上面,你会发现这个技术可以让你很好的利用多CPU的优势。
(2) 另外一个解决GIL的策略是使用C扩展编程技术。 主要思想是将计算密集型任务转移给C,跟Python独立,在工作的时候在C代码中释放GIL。
#include "Python.h"
...
PyObject *pyfunc(PyObject *self, PyObject *args) {
...
Py_BEGIN_ALLOW_THREADS
// Threaded C code
...
Py_END_ALLOW_THREADS
...
}
如果你使用其他工具访问C语言,比如对于Cython的ctypes库,你不需要做任何事。 例如,ctypes在调用C时会自动释放GIL。见文章