0x03 线程专有状态

问题

有些情况下, 我们需要保存当前运行线程的专有状态, 而且这个状态对其他线程是不可见的, 即其他线程不会对这个状态产生影响, 做出修改. 在这多线程程序中是很常见的需求.

可以通过threading.local()创建一个线程本地存储对象, 将需要保存的变量, 以这个对象属性的形式进行保存和读取. 只对当前运行的线程可见, 其他线程无法感知.

使用方法

socket编程为例, 每个线程管理一个独立的socket, 以避免相互之间的干扰.

import threading
from functools import partial
from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, types=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = types
        self.local = threading.local()
        print("type of local:", type(self.local))

    def __enter__(self):
        if hasattr(self.local, "sock"):
            raise RuntimeError("already connected")
        self.local.sock = socket(self.family, self.type)
        self.local.sock.connect(self.address)
        return self.local.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.local.sock.close()
        del self.local.sock

def test(conn):
    with conn as s:
        s.send(b"GET /index.html HTTP/1.0\r\n")
        s.send(b"Host: www.python.org\r\n")
        s.send(b"\r\n")
        res = b"".join(iter(partial(s.recv, 8192), b""))
    print("Got {} bytes".format(len(res)))

conn = LazyConnection(("www.python.org", 80))
t1 = threading.Thread(target=test, args=(conn,))
t2 = threading.Thread(target=test, args=(conn,))
t1.start()
t2.start()
t1.join()
t2.join()

输出为:

type of local: <class '_thread._local'>
Got 392 bytes
Got 392 bytes

所有线程都公用一个LazyConnection的实例. 实例对象的local属性供每个线程单独使用, 每个线程将自己所操作的独立的socket保存为local对象的sock属性. 这样LazyConnection就能安全地用于多线程环境中了.

每个线程实际上都创建了自己专属的socket连接, 以self.local.sock的形式保存. 当不同的线程在socket上执行操作时, 它们并不会互相产生影响, 因为它们都是在不同的socket上完成操作的.

最后更新于