一、多线程
1、由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading
模块有个current_thread()
函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread
,子线程的名字在创建时指定,我们用LoopThread
命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1
,Thread-2
……多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
import time
import threading
lock = threading.Lock()
def matter1(music):
for i in range(0,len(music)):
print("第" + str(i + 1) + "首歌是:" + str(music[i]))
# 假设每一首歌曲的时间是2秒
time.sleep(1)
print("切换下一首歌..%s."% threading.current_thread().name)
def matter2(number):
lock.acquire()
j = 0
while j <= number:
print("我准备写入第" + str(j + 1) +"行代码")
j = j + 1
# 假设每写一行代码的时间为1秒
time.sleep(1)
print("写下一行代码%s..."% threading.current_thread().name)
lock.release()
def matter3(snacks):
lock.acquire()
for k in range(0,len(snacks)):
print("我正在听着歌吃" + str(snacks[k]) + "零食")
#每吃一袋零食间隔5秒
time.sleep(1)
print("吃完了一包零食%s"% threading.current_thread().name)
lock.release()
def multi_thread():
begin = time.time()
args_list = {"matter1":["蓝莲花", "曾经的你", "光阴的故事"],
"matter2":3,
"matter3":["辣条", "薯片", "锅巴"],}
thread_list = []
funcname = [matter1, matter2, matter3]
func_name = ["matter1","matter2","matter3"]
for fun in zip(func_name, funcname):
pro = threading.Thread(target = fun[1], args=(args_list[fun[0]],), name="线程" + fun[0])
thread_list.append(pro)
for t in thread_list:
t.setDaemon(True)
t.start()
for t in thread_list:
t.join()
end = time.time()
runtime = end - begin
print(runtime)
if __name__ == "__main__":
multi_thread()
2、对 ThreadLocal 的理解
是全局变量,但是每个线程都有它的镜像。
一、对 ThreadLocal 的理解
ThreadLocal,有的人叫它线程本地变量,也有的人叫它线程本地存储,其实意思一样。 ThreadLocal 在每一个变量中都会创建一个副本,每个线程都可以访问自己内部的副本变量。
二、为什么会出现 ThreadLocal 的技术应用
我们知道多线程环境下,每一个线程均可以使用所属进程的全局变量。如果一个线程对全局变量进行了修改,将会影响到其他所有的线程对全局变量的计算操作,从而出现数据混乱,即为脏数据。为了避免逗哥线程同时对变量进行修改,引入了线程同步机制,通过互斥锁、条件变量或者读写锁来控制对全局变量的访问。
只用全局变量并不能满足多线程环境的需求,很多时候线程还需要拥有自己的私有数据,这些数据对于其他线程来说是不可见的。因此线程中也可以使用局部变量,局部变量只有线程自身可以访问,同一个进程下的其他线程不可访问。
有时候使用局部变量不太方便,因此 Python 还提供了ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的。
ThreadLocal 真正做到了线程之间的数据隔离。
import threading
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
# 获取当前线程关联的student:
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
ThreadLocal
最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。