python 多线程编程

轮询执行线程函数,设置超时器

# 轮训查询一个变量,判断是否停止, 如果线程卡在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 对象最好单次使用,就是说,你创建一个 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()

Condition

Condition 实现了一个周期定时器,每当定时器超时的时候,其他线程都可以监测

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 全局锁

尽管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。见文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝鲸123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值