Skip to content

Commit 3d58508

Browse files
committed
add ThreadLocal.md
1 parent e45750e commit 3d58508

1 file changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,186 @@
11
# ThreadLocal
22

3+
我们知道,同一进程的多个线程之间是内存共享的,这意味着,当一个线程对全局变量做了修改,将会影响到其他所有线程,这是很危险的。为了避免多个线程同时修改全局变量,我们就需要对全局变量的修改加锁。
4+
5+
除了对全局变量的修改进行加锁,你可能也想到了可以使用线程自己的局部变量,因为局部变量只有线程自己能看见,对同一进程的其他线程是不可访问的。确实如此,让我们先看一个例子:
6+
7+
```python
8+
from threading import Thread, current_thread
9+
10+
def echo(num):
11+
print current_thread().name, num
12+
13+
def calc():
14+
print 'thread %s is running...' % current_thread().name
15+
local_num = 0
16+
for _ in xrange(10000):
17+
local_num += 1
18+
echo(local_num)
19+
print 'thread %s ended.' % current_thread().name
20+
21+
if __name__ == '__main__':
22+
print 'thread %s is running...' % current_thread().name
23+
24+
threads = []
25+
for i in range(5):
26+
threads.append(Thread(target=calc))
27+
threads[i].start()
28+
for i in range(5):
29+
threads[i].join()
30+
31+
print 'thread %s ended.' % current_thread().name
32+
```
33+
34+
在上面的代码中,我们创建了 5 个线程,每个线程都对自己的局部变量 local_num 进行 10000 次的加 1 操作。由于对线程局部变量的修改不会影响到其他线程,因此,我们可以看到,每个线程结束时打印的 local_num 的值都为 10000,执行结果如下:
35+
36+
```python
37+
thread MainThread is running...
38+
thread Thread-4 is running...
39+
Thread-4 10000
40+
thread Thread-4 ended.
41+
thread Thread-5 is running...
42+
Thread-5 10000
43+
thread Thread-5 ended.
44+
thread Thread-6 is running...
45+
Thread-6 10000
46+
thread Thread-6 ended.
47+
thread Thread-7 is running...
48+
Thread-7 10000
49+
thread Thread-7 ended.
50+
thread Thread-8 is running...
51+
Thread-8 10000
52+
thread Thread-8 ended.
53+
thread MainThread ended.
54+
```
55+
56+
上面这种**线程使用自己的局部变量**的方法虽然可以避免多线程对同一变量的访问冲突,但还是有一些问题。在实际的开发中,我们会调用很多函数,每个函数又有很多个局部变量,这时每个函数都这么传参数显然是不可取的。
57+
58+
为了解决这个问题,一个比较容易想到的做法就是创建一个全局字典,以线程的 ID 作为 key,线程的局部数据作为 value,这样就可以消除函数传参的问题,代码如下:
59+
60+
```python
61+
from threading import Thread, current_thread
62+
63+
global_dict = {}
64+
65+
def echo():
66+
num = global_dict[current_thread()] # 线程根据自己的 ID 获取数据
67+
print current_thread().name, num
68+
69+
def calc():
70+
print 'thread %s is running...' % current_thread().name
71+
72+
global_dict[current_thread()] = 0
73+
for _ in xrange(10000):
74+
global_dict[current_thread()] += 1
75+
echo()
76+
77+
print 'thread %s ended.' % current_thread().name
78+
79+
if __name__ == '__main__':
80+
print 'thread %s is running...' % current_thread().name
81+
82+
threads = []
83+
for i in range(5):
84+
threads.append(Thread(target=calc))
85+
threads[i].start()
86+
for i in range(5):
87+
threads[i].join()
88+
89+
print 'thread %s ended.' % current_thread().name
90+
```
91+
92+
看下执行结果:
93+
94+
```
95+
thread MainThread is running...
96+
thread Thread-64 is running...
97+
thread Thread-65 is running...
98+
thread Thread-66 is running...
99+
thread Thread-67 is running...
100+
thread Thread-68 is running...
101+
Thread-67 10000
102+
thread Thread-67 ended.
103+
Thread-65 10000
104+
thread Thread-65 ended.
105+
Thread-68 10000
106+
thread Thread-68 ended.
107+
Thread-66 10000
108+
thread Thread-66 ended.
109+
Thread-64 10000
110+
thread Thread-64 ended.
111+
thread MainThread ended.
112+
```
113+
114+
上面的做法虽然消除了函数传参的问题,但是还是有些不完美,为了获取线程的局部数据,我们需要先获取线程 ID,另外,global_dict 是个全局变量,所有线程都可以对它进行修改,还是有些危险。
115+
116+
那到底如何是好?
117+
118+
事实上,Python 提供了 ThreadLocal 对象,它真正做到了线程之间的数据隔离,而且不用查找 dict,代码如下:
119+
120+
```python
121+
from threading import Thread, current_thread, local
122+
123+
global_data = local()
124+
125+
def echo():
126+
num = global_data.num
127+
print current_thread().name, num
128+
129+
def calc():
130+
print 'thread %s is running...' % current_thread().name
131+
132+
global_data.num = 0
133+
for _ in xrange(10000):
134+
global_data.num += 1
135+
echo()
136+
137+
print 'thread %s ended.' % current_thread().name
138+
139+
if __name__ == '__main__':
140+
print 'thread %s is running...' % current_thread().name
141+
142+
threads = []
143+
for i in range(5):
144+
threads.append(Thread(target=calc))
145+
threads[i].start()
146+
for i in range(5):
147+
threads[i].join()
148+
149+
print 'thread %s ended.' % current_thread().name
150+
```
151+
152+
在上面的代码中,global_data 就是 ThreadLocal 对象,你可以把它当作一个全局变量,但它的每个属性,比如 `global_data.num` 都是线程的局部变量,没有访问冲突的问题。
153+
154+
让我们看下执行结果:
155+
156+
```
157+
thread MainThread is running...
158+
thread Thread-94 is running...
159+
thread Thread-95 is running...
160+
thread Thread-96 is running...
161+
thread Thread-97 is running...
162+
thread Thread-98 is running...
163+
Thread-96 10000
164+
thread Thread-96 ended.
165+
Thread-97 10000
166+
thread Thread-97 ended.
167+
Thread-95 10000
168+
thread Thread-95 ended.
169+
Thread-98 10000
170+
thread Thread-98 ended.
171+
Thread-94 10000
172+
thread Thread-94 ended.
173+
thread MainThread ended.
174+
```
175+
176+
# 小结
177+
178+
- 使用 ThreadLocal 对象来线程绑定自己独有的数据。
179+
180+
# 参考资料
181+
182+
- [ThreadLocal - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386832845200f6513494f0c64bd882f25818a0281e80000)
183+
- [深入理解Python中的ThreadLocal变量(上) | Just For Fun](http://selfboot.cn/2016/08/22/threadlocal_overview/)
184+
- [Python线程同步机制 | Python见闻志](https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_thread_sync.html)
185+
186+

0 commit comments

Comments
 (0)