|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -You need to store state that’s specific to the currently executing thread and not visible |
9 |
| -to other threads. |
| 8 | +你需要保存正在运行线程的状态,这个状态对于其他的线程是不可见的。 |
10 | 9 |
|
11 | 10 | |
|
12 | 11 |
|
13 | 12 | ----------
|
14 | 13 | 解决方案
|
15 | 14 | ----------
|
16 |
| -Sometimes in multithreaded programs, you need to store data that is only specific to |
17 |
| -the currently executing thread. To do this, create a thread-local storage object using |
18 |
| -threading.local(). Attributes stored and read on this object are only visible to the |
19 |
| -executing thread and no others. |
20 |
| -As an interesting practical example of using thread-local storage, consider the LazyCon |
21 |
| -nection context-manager class that was first defined in Recipe 8.3. Here is a slightly |
22 |
| -modified version that safely works with multiple threads: |
23 |
| - |
24 |
| -from socket import socket, AF_INET, SOCK_STREAM |
25 |
| -import threading |
26 |
| - |
27 |
| -class LazyConnection: |
28 |
| - def __init__(self, address, family=AF_INET, type=SOCK_STREAM): |
29 |
| - self.address = address |
30 |
| - self.family = AF_INET |
31 |
| - self.type = SOCK_STREAM |
32 |
| - self.local = threading.local() |
33 |
| - |
34 |
| - def __enter__(self): |
35 |
| - if hasattr(self.local, 'sock'): |
36 |
| - raise RuntimeError('Already connected') |
37 |
| - self.local.sock = socket(self.family, self.type) |
38 |
| - self.local.sock.connect(self.address) |
39 |
| - return self.local.sock |
40 |
| - |
41 |
| - def __exit__(self, exc_ty, exc_val, tb): |
42 |
| - self.local.sock.close() |
43 |
| - del self.local.sock |
44 |
| - |
45 |
| -In this code, carefully observe the use of the self.local attribute. It is initialized as an |
46 |
| -instance of threading.local(). The other methods then manipulate a socket that’s |
47 |
| -stored as self.local.sock. This is enough to make it possible to safely use an instance |
48 |
| -of LazyConnection in multiple threads. For example: |
49 |
| - |
50 |
| -from functools import partial |
51 |
| -def test(conn): |
52 |
| - with conn as s: |
53 |
| - s.send(b'GET /index.html HTTP/1.0\r\n') |
54 |
| - s.send(b'Host: www.python.org\r\n') |
55 |
| - |
56 |
| - s.send(b'\r\n') |
57 |
| - resp = b''.join(iter(partial(s.recv, 8192), b'')) |
58 |
| - |
59 |
| - print('Got {} bytes'.format(len(resp))) |
60 |
| - |
61 |
| -if __name__ == '__main__': |
62 |
| - conn = LazyConnection(('www.python.org', 80)) |
63 |
| - |
64 |
| - t1 = threading.Thread(target=test, args=(conn,)) |
65 |
| - t2 = threading.Thread(target=test, args=(conn,)) |
66 |
| - t1.start() |
67 |
| - t2.start() |
68 |
| - t1.join() |
69 |
| - t2.join() |
70 |
| - |
71 |
| -The reason it works is that each thread actually creates its own dedicated socket con‐ |
72 |
| -nection (stored as self.local.sock). Thus, when the different threads perform socket |
73 |
| -operations, they don’t interfere with one another as they are being performed on dif‐ |
74 |
| -ferent sockets. |
| 15 | +有时在多线程编程中,你需要只保存当前运行线程的状态。 |
| 16 | +要这么做,可使用 ``thread.local()`` 创建一个本地线程存储对象。 |
| 17 | +对这个对象的属性的保存和读取操作都只会对执行线程可见,而其他线程并不可见。 |
| 18 | + |
| 19 | +作为使用本地存储的一个有趣的实际例子, |
| 20 | +考虑在8.3小节定义过的 ``LazyConnection`` 上下文管理器类。 |
| 21 | +下面我们对它进行一些小的修改使得它可以适用于多线程: |
| 22 | + |
| 23 | +.. code-block:: python |
| 24 | +
|
| 25 | + from socket import socket, AF_INET, SOCK_STREAM |
| 26 | + import threading |
| 27 | +
|
| 28 | + class LazyConnection: |
| 29 | + def __init__(self, address, family=AF_INET, type=SOCK_STREAM): |
| 30 | + self.address = address |
| 31 | + self.family = AF_INET |
| 32 | + self.type = SOCK_STREAM |
| 33 | + self.local = threading.local() |
| 34 | +
|
| 35 | + def __enter__(self): |
| 36 | + if hasattr(self.local, 'sock'): |
| 37 | + raise RuntimeError('Already connected') |
| 38 | + self.local.sock = socket(self.family, self.type) |
| 39 | + self.local.sock.connect(self.address) |
| 40 | + return self.local.sock |
| 41 | +
|
| 42 | + def __exit__(self, exc_ty, exc_val, tb): |
| 43 | + self.local.sock.close() |
| 44 | + del self.local.sock |
| 45 | +
|
| 46 | +代码中,自己观察对于 ``self.local`` 属性的使用。 |
| 47 | +它被初始化尾一个 ``threading.local()`` 实例。 |
| 48 | +其他方法操作被存储为 ``self.local.sock`` 的套接字对象。 |
| 49 | +有了这些就可以在多线程中安全的使用 ``LazyConnection`` 实例了。例如: |
| 50 | + |
| 51 | +:: |
| 52 | + |
| 53 | + from functools import partial |
| 54 | + def test(conn): |
| 55 | + with conn as s: |
| 56 | + s.send(b'GET /index.html HTTP/1.0\r\n') |
| 57 | + s.send(b'Host: www.python.org\r\n') |
| 58 | + |
| 59 | + s.send(b'\r\n') |
| 60 | + resp = b''.join(iter(partial(s.recv, 8192), b'')) |
| 61 | + |
| 62 | + print('Got {} bytes'.format(len(resp))) |
| 63 | + |
| 64 | + if __name__ == '__main__': |
| 65 | + conn = LazyConnection(('www.python.org', 80)) |
| 66 | + |
| 67 | + t1 = threading.Thread(target=test, args=(conn,)) |
| 68 | + t2 = threading.Thread(target=test, args=(conn,)) |
| 69 | + t1.start() |
| 70 | + t2.start() |
| 71 | + t1.join() |
| 72 | + t2.join() |
| 73 | + |
| 74 | +它之所以行得通的原因是每个线程会创建一个自己专属的套接字连接(存储为self.local.sock)。 |
| 75 | +因此,当不同的线程执行套接字操作时,由于操作的是不同的套接字,因此它们不会相互影响。 |
75 | 76 |
|
76 | 77 | |
|
77 | 78 |
|
78 | 79 | ----------
|
79 | 80 | 讨论
|
80 | 81 | ----------
|
81 |
| -Creating and manipulating thread-specific state is not a problem that often arises in |
82 |
| -most programs. However, when it does, it commonly involves situations where an object |
83 |
| -being used by multiple threads needs to manipulate some kind of dedicated system |
84 |
| -resource, such as a socket or file. You can’t just have a single socket object shared by |
85 |
| -everyone because chaos would ensue if multiple threads ever started reading and writing |
86 |
| -on it at the same time. Thread-local storage fixes this by making such resources only |
87 |
| -visible in the thread where they’re being used. |
88 |
| -In this recipe, the use of threading.local() makes the LazyConnection class support |
89 |
| -one connection per thread, as opposed to one connection for the entire process. It’s a |
90 |
| -subtle but interesting distinction. |
91 |
| -Under the covers, an instance of threading.local() maintains a separate instance |
92 |
| -dictionary for each thread. All of the usual instance operations of getting, setting, and |
93 |
| -deleting values just manipulate the per-thread dictionary. The fact that each thread uses |
94 |
| -a separate dictionary is what provides the isolation of data. |
| 82 | +在大部分程序中创建和操作线程特定状态并不会有什么问题。 |
| 83 | +不过,当出了问题的时候,通常是因为某个对象被多个线程使用到,用来操作一些专用的系统资源, |
| 84 | +比如一个套接字或文件。你不能让所有线程贡献一个单独对象, |
| 85 | +因为多个线程同时读和写的时候会产生混乱。 |
| 86 | +本地线程存储通过让这些资源只能在被使用的线程中可见来解决这个问题。 |
| 87 | + |
| 88 | +本节中,使用 ``thread.local()`` 可以让 ``LazyConnection`` 类支持一个线程一个连接, |
| 89 | +而不是对于所有的进程都只有一个连接。 |
| 90 | + |
| 91 | +其原理是,每个 ``threading.local()`` 实例为每个线程维护着一个单独的实例字典。 |
| 92 | +所有普通实例操作比如获取、修改和删除值仅仅操作这个字典。 |
| 93 | +每个线程使用一个独立的字典就可以保证数据的隔离了。 |
| 94 | + |
0 commit comments