python 的标准库,提供了两个线程模块,分别为 _thread 和 threading 模块,_thread 为低端模块,threading为高端模块,对_thread进行了分装,大多数情况下,我们只用到 threading 模块。
启动一个线程,就是把一个函数传入,并创建一个Thread实例,然后调用 start() 函数开始执行。
例子如下:
import threading
import time
def worker():
print("thread %s is running..." % threading.current_thread().name)
n =0
while n <5:
n += 1
print("thread %s >>>>%s" %(threading.current_thread().name,n))
time.sleep(1)
print("thread %s end" % threading.current_thread().name)
print("thread %s is running" % threading.current_thread().name)
t = threading.Thread(target = worker, name = 'WorkerThread')
t.start()
t.join()
print("thread %s end" %threading.current_thread().name)
运行结果如下:
thread MainThread is running
thread WorkerThread is running...
thread WorkerThread >>>>1
thread WorkerThread >>>>2
thread WorkerThread >>>>3
thread WorkerThread >>>>4
thread WorkerThread >>>>5
thread WorkerThread end
thread MainThread end
由于任何进程都会默认启动一个线程, 我们把该线程成为主线程,主线程又会启动新的线程,python的threading 模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用WorkerThread命名子线程。 名字仅仅在打印时使用,完全没有其他意义。如果不起名字 python就会自动给线程命名为Thread-1, Thread-2.。。。
Lock() 锁
多线程和多进程的最大区别在于,多进程中,同一个变量,各自有一份拷贝在于每个进程中,互补影响,而多线程中,所有变量都有所有线程共享,所以任何一个变量都可以被任何一个线程修改。因此,线程之间共享数据的最大的危险在于多个线程同时修改一个变量,把内容给改乱了。之所以会改乱,是因为进程的调度是有操作系统完成的,当线程交替进行,就有可能把数据改乱。
为了避免这个问题,可以使用在每个以需要一个线程操作的地方 加上一把锁即可,创建一把锁,就是通过threading.Lock()实现。
实例如下:
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
当多个线程同时执行 lock.acquire()函数时,只有一个线程能够成功获得锁,其他线程就只能等待,知道获取锁
注意: 获得锁线程用完锁后一定要释放锁即lock.release(), 这样其他线程才不会一直等待,变成死线程。所以我们使用try..finally 来确保锁一定会被释放。
多核cpu
启动与cpu数量相同的线程:
import threading
def loop():
x = 0
while True:
x = x + 1
for i in range(threading.cpu_count()):
t = threading.Thread(target = loop)
t.start()
会发现,在4核CPU上可以监控到CPU占用率仅有102%,也就是仅使用了一核。这是因为 因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。