1. 简介
在Python中,线程是轻量级的执行单元,每个线程都有自己的执行序列,且并发执行。线程模块threading是Python中使用最广泛的模块之一,它提供了一种创建并发线程的方法,且与操作系统无关。
2. 线程的创建
2.1 threading.Thread()
使用threading.Thread()方法可以创建一个线程对象,如下所示:
import threading
#定义一个函数作为线程的执行体
def thread_main(name):
print("Thread %s is running" % name)
def main():
#创建线程1,指定函数thread_main为线程的执行体,传递参数
t1 = threading.Thread(target=thread_main, args=("Thread_1",))
#创建线程2,指定函数thread_main为线程的执行体,传递参数
t2 = threading.Thread(target=thread_main, args=("Thread_2",))
#让线程1开始执行
t1.start()
#让线程2开始执行
t2.start()
if __name__ == '__main__':
main()
在上面的代码中,我们使用threading.Thread()方法创建了两个线程对象t1和t2,指定了它们的执行函数和参数,然后分别启动这两个线程。运行代码输出结果如下:
Thread Thread_1 is running
Thread Thread_2 is running
由此可见,两个线程是并发执行的。
2.2 继承Thread类
除了可以使用threading.Thread()方法创建线程对象外,还可以通过继承Thread类来实现创建线程的方法,如下所示:
import threading
#创建一个MyThread类,继承Thread类
class MyThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
print("Thread %s is running" % self.name)
def main():
t1 = MyThread("Thread_1")
t2 = MyThread("Thread_2")
t1.start()
t2.start()
if __name__ == '__main__':
main()
在上面的代码中,我们创建了一个MyThread类,继承了Thread类,并重载了其中的run()方法,用于指定线程的执行体。接着,我们创建了两个MyThread对象t1和t2,并启动这两个线程。运行代码输出结果如下:
Thread Thread_1 is running
Thread Thread_2 is running
与2.1中的代码输出结果相同,表明两个线程也是并发执行的。
3. 线程的运行状态
线程的生命周期可分为5个状态:
新建状态(New):线程被创建但还没有开始执行。
就绪状态(Ready):等待CPU调度,一旦得到CPU资源就立即开始执行。
运行状态(Running):线程正在执行。
阻塞状态(Blocked):执行过程中被暂停,等待某个条件的出现,如I/O操作。
终止状态(Terminated):线程执行结束或发生异常而终止。
4. 线程的属性和方法
4.1 属性
4.1.1 name属性
Thread对象的name属性用于获取或设置线程名称,默认名称为Thread-i,i为线程的编号。
import threading
def thread_main(name):
print("Thread %s is running" % name)
def main():
t1 = threading.Thread(target=thread_main, args=("Thread_1",), name="MyThread_1")
t2 = threading.Thread(target=thread_main, args=("Thread_2",), name="MyThread_2")
print(t1.name) #输出结果:MyThread_1
print(t2.name) #输出结果:MyThread_2
t1.start()
t2.start()
if __name__ == '__main__':
main()
在上面的代码中,我们创建了两个线程对象t1和t2,分别指定了它们的name属性。运行代码输出结果如下:
MyThread_1
MyThread_2
Thread Thread_1 is running
Thread Thread_2 is running
由此可见,线程的name属性可用于获取或设置线程的名称。
4.1.2 ident属性
Thread对象的ident属性用于获取线程的标识符,如果线程还没有开始执行,则id值为None。
import threading
def thread_main(name):
print("Thread %s id is %s" % (name, threading.currentThread().ident))
def main():
t1 = threading.Thread(target=thread_main, args=("Thread_1",))
t2 = threading.Thread(target=thread_main, args=("Thread_2",))
print(t1.ident) #输出结果:None
print(t2.ident) #输出结果:None
t1.start()
t2.start()
if __name__ == '__main__':
main()
在上面的代码中,我们创建了两个线程对象t1和t2,分别获取了它们的ident属性。运行代码输出结果如下:
None
None
Thread Thread_1 id is 123145103792384
Thread Thread_2 id is 123145111185088
由此可见,在线程还没有开始执行时,ident属性的值为None,当线程开始执行时,ident属性的值才会被赋值。
4.2 方法
4.2.1 start()方法
Thread对象的start()方法用于启动线程,使其进入就绪状态,等待CPU调度执行。
start()方法只能被调用一次,如果多次调用会抛出RuntimeError异常。
4.2.2 run()方法
Thread对象的run()方法用于指定线程的执行体。默认情况下,run()方法会调用target参数指定的函数。
当使用继承Thread类来创建线程时,必须重载run()方法,并在其中指定线程的执行体。
4.2.3 join([timeout])方法
Thread对象的join([timeout])方法用于使主线程等待子线程结束。
timeout参数指定等待的最长时间(秒),如果在指定时间内线程还没有结束,则不再等待,返回None。
import threading
import time
def thread_main(name):
print("Thread %s is running" % name)
time.sleep(1) #模拟线程执行耗时
print("Thread %s is done" % name)
def main():
t1 = threading.Thread(target=thread_main, args=("Thread_1",))
t2 = threading.Thread(target=thread_main, args=("Thread_2",))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == '__main__':
main()
在上面的代码中,我们创建了两个线程对象t1和t2,在主线程中依次启动它们。接着,调用t1.join()和t2.join()方法,使主线程等待t1和t2线程结束。运行代码输出结果如下:
Thread Thread_1 is running
Thread Thread_2 is running
Thread Thread_1 is done
Thread Thread_2 is done
由此可见,join()方法能够保证在子线程结束前不会退出主线程。
4.2.4 isAlive()方法
Thread对象的isAlive()方法用于判断线程是否还存在。
如果线程还存在,则返回True,否则返回False。
import threading
import time
def thread_main(name):
print("Thread %s is running" % name)
time.sleep(1) #模拟线程执行耗时
print("Thread %s is done" % name)
def main():
t1 = threading.Thread(target=thread_main, args=("Thread_1",))
t2 = threading.Thread(target=thread_main, args=("Thread_2",))
t1.start()
t2.start()
time.sleep(0.5) #等待线程执行一段时间
print(t1.isAlive()) #输出结果:True
print(t2.isAlive()) #输出结果:True
t1.join()
t2.join()
print(t1.isAlive()) #输出结果:False
print(t2.isAlive()) #输出结果:False
if __name__ == '__main__':
main()
在上面的代码中,我们创建了两个线程对象t1和t2,并启动它们。然后等待线程执行一段时间后,依次输出它们的isAlive()方法的返回值。接着,等待两个线程执行结束后,再次输出它们的isAlive()方法的返回值。运行代码输出结果如下:
Thread Thread_1 is running
Thread Thread_2 is running
True
True
Thread Thread_1 is done
Thread Thread_2 is done
False
False
由此可见,通过isAlive()方法可以判断线程是否还存在。
5. 线程同步
对于多个线程对同一资源进行读写操作时,会出现竞争状态,可能导致数据不一致或数据丢失等问题。为了解决这种问题,需要使用线程同步技术。
5.1 Lock对象
Lock对象用于控制多个线程对共享资源的访问,当一个线程获得了Lock对象的锁(调用acquire()方法),其他线程就不能再访问共享资源,只有当该线程释放了锁(调用release()方法)后,其他线程才可以再次获得锁。
import threading
import time
shared_resource = 0 #共享资源
lock = threading.Lock() #Lock对象
#函数作为线程的执行体
def thread_main():
global shared_resource
lock.acquire() #获得锁
for i in range(1000000):
shared_resource += 1
lock.release() #释放锁
def main():
threads = []
for i in range(4):
threads.append(threading.Thread(target=thread_main))
threads[i].start()
for i in range(4):
threads[i].join()
print(shared_resource)
if __name__ == '__main__':
main()
在上面的代码中,我们定义了一个全局变量shared_resource,并创建了一个Lock对象lock作为全局变量。然后,我们定义了一个thread_main()函数作为线程的执行体,该函数在获得锁并修改全局变量shared_resource后,释放锁。
在主线程中,我们创建了4个线程,并启动它们。然后等待它们执行结束,并输出最终的共享资源shared_resource。
运行代码输出结果如下:
4000000
由此可见,通过Lock对象可以控制多个线程对共享资源的访问,避免了竞态条件。
6. 总结
本文介绍了Python中线程的相关知识,包括线程的创建、线程的运行状态、线程的属性和方法、线程同步等内容。通过学习本文,读者可以初步了解Python中线程的基本用法,并能通过使用Lock对象等线程同步技术,避免多个线程并发执行时出现的竞态条件。