python中,有两个标准模块thread和threading可以实现多线程,不过threading更加高级,推荐使用threading。
threading 模块提供的常用方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
- threading.setName 设置线程名
- threading.getName 获得线程名
- threading.Timer() 定时器,,每隔一段时间就开一一个线程调用一个函数
- threading.Thread(target=方法名, name='线程名', args=参数)启动一个线程,返回一个进程对象 。
这里以主线程A,子线程B为例:
- start() 调用B.start()开始执行子线程
- join() 调用B。join开始执行,同时主线程会在调用的地方等待
- setDaemon() 主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程, 要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出(和join相反)。必须在start() 方法调用之前设置
threading 模块提供的常量:
threading.TIMEOUT_MAX 设置threading全局超时时间。
线程锁
多线程中,除了线程自己的临时变量, 所有全局或静态变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改。
因此,需要引入线程锁给变量上一把锁,当某个线程开始执行时,我们说,该线程因为获得了锁,因此其他线程不能同时执行,只能等待。
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.acquire()后的代码。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。
python中, 线程对多核cpu无效
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。
链接:https://www.jianshu.com/p/eabd0c8d338c
锁机制
在了解锁机制前,我们先来看一下下面这个例子:
## 使用多线程进行加法运算
import threading
# 定义全局变量VALUE
VALUE = 0
# 定义加法线程函数
def add_value():
global VALUE
for x in range(1000000):
VALUE += 1
print('value = ', VALUE)
# 定义两个线程并发执行加法操作
def add_thread_main():
for x in range(2):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__':
add_thread_main()
"""
Output:
value = 1147074
value = 1211397
"""
上面的示例按照我们的逻辑看来应该是依次输出1000000和2000000,但结果并不是这样的,这就是常说的多线程共享全局变量问题。其实在我们执行线程时,执行的顺序是不一定的,也就是说有时候可能重合在一起执行,因而导致有时虽二者都对共享变量进行了一次加法(即本应加两次)而实际上只真正加了一次。
而为了解决这样的问题,threading模块提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他线程此时不能访问该变量,直到加锁线程处理完控制变量并把锁释放了,其他线程才能进行访问处理。锁机制使用起来也很简单,由于是多个线程访问共享变量,因而需设置一个全局的Lock类对象,然后在访问前后分别使用Lock类的acquire()方法加锁和release()方法释放锁。
上述例子使用锁机制仅需做以下几处的修改:
## 使用多线程进行加法运算
import threading
# 定义全局变量VALUE
VALUE = 0
gLock = threading.Lock()
# 定义加法线程函数
def add_value():
global VALUE
gLock.acquire()
for x in range(100000):
VALUE += 1
print('value = ', VALUE)
gLock.release()
# 定义两个线程并发执行加法操作
def add_thread_main():
for x in range(4):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__':
add_thread_main()
“””
output:
value = 100000
value = 200000
value = 300000
value = 400000
"""