12.6 �存线程的状�信�¶

问题¶

你需��存正在�行线程的状�,这个状�对于其他的线程是���的。

解决方案¶

有时在多线程编程中,你需���存当��行线程的状�。 �这么�,�使用 thread.local() 创建一个本地线程存储对象。 对这个对象的属性的�存和读��作都�会对执行线程��,而其他线程并���。

作为使用本地存储的一个有趣的实际例�, 考虑在8.3�节定义过的 LazyConnection 上下文管�器类。 下�我们对它进行一些�的修改使得它�以适用于多线程:

from socket import socket, AF_INET, SOCK_STREAM
import threading

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = AF_INET
        self.type = SOCK_STREAM
        self.local = threading.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

代�中,自己观察对于 self.local 属性的使用。 它被�始化为一个 threading.local() 实例。 其他方法�作被存储为 self.local.sock 的套接字对象。 有了这些就�以在多线程中安全的使用 LazyConnection 实例了。例如:

from functools import partial
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')
        resp = b''.join(iter(partial(s.recv, 8192), b''))

    print('Got {} bytes'.format(len(resp)))

if __name__ == '__main__':
    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()

它之所以行得通的原因是�个线程会创建一个自己专属的套接字连接(存储为self.local.sock)。 因此,当��的线程执行套接字�作时,由于�作的是��的套接字,因此它们�会相互影�。

讨论¶

在大部分程�中创建和�作线程特定状�并�会有什么问题。 �过,当出了问题的时候,通常是因为�个对象被多个线程使用到,用��作一些专用的系统资�, 比如一个套接字或文件。你�能让所有线程共享一个�独对象, 因为多个线程�时读和写的时候会产生混乱。 本地线程存储通过让这些资��能在被使用的线程中���解决这个问题。

本节中,使用 thread.local() �以让 LazyConnection 类支�一个线程一个连接, 而�是对于所有的进程都�有一个连接。

其原�是,�个 threading.local() 实例为�个线程维护�一个�独的实例字典。 所有普通实例�作比如获��修改和删除值仅仅�作这个字典。 �个线程使用一个独立的字典就�以��数�的隔离了。