threading模块
线程简述
线程(轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。可以将它们认为是在一个主进程或"主线程"中并行运行的一些"迷你进程"。
线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录运行的上下文。它其他线程运行时,它可以被抢占(中断)和临时挂起(睡眠/加锁)---这种做法叫做让步(yielding)。
多线程的创建
使用Thread类,可以有很多方法来创建线程,其中常用的有:
- 创建Thread的示例,传给它一个函数;
- 派生Thread的子类,重新run方法,并创建子类的实例。
示例1:创建Thread的实例,传给它一个函数
from threading import Thread
import time
def test():
print("---hello-world---")
time.sleep(1)
for i in range(5):
#创建线程,线程执行的任务是target指定的函数,如果函数需要传入参数,则可以指定args=(),或者kwargs={}
t = Thread(target=test)
t.start()
运行结果:
---hello-world---
---hello-world---
---hello-world---
---hello-world---
---hello-world---
示例2:使用Thread子类创建线程
import threading
import time
# 自定义类继承threading类
class myThread(threading.Thread):
# 重新run方法
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm " + self.name+' @ '+str(i)
print(msg)
if __name__ == "__main__":
# 创建线程
t = myThread()
t.start()
运行结果:
I'm Thread-1 @ 0
I'm Thread-1 @ 1
I'm Thread-1 @ 2
python的threading.Thread
类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
多线程共享全局变量
在一个进程中,多个线程之间是共享全局变量的,即一个线程修改了全局变量,另外一个线程在此之后获取的这个全局变量是被修改后的。比如下面例子:
from threading import Thread
import time
num = 100
def thread1():
global num
for i in range(3):
num += 1
print("I'am Thread1 ." + " my num is "+str(num))
def thread2():
print("I'am Thread2. " +" my num is "+ str(num))
t1 = Thread(target=thread1)
t1.start()
# 让程序睡眠1秒钟,确保线程1执行完毕。
time.sleep(1)
t2 = Thread(target=thread2)
t2.start()
运行结果:
I'am Thread1. my num is 103
I'am Thread2. my num is 103
线程关于全局变量注意点
在一个进程内的所有线程共享全局变量,能够在不适用其他方式的前提下完成多线程之间的数据共享(这点要比多进程要好)
一个进程中的各个线程与主线程共享同一片数据空间。因此与进程相比,线程之间的信息共享和通信更加容易。在一个程序中,线程的执行是:每个线程运行一小会,然后让步给其他线程(再次排队等待更多的CPU时间)。在整个进程的执行过程中,每个线程执行它自己特定的任务,在必要时和其他线程进行通信。
当然,这种共享是有风险的。如果两个或多个线程访问同一片数据,由于数据的访问顺序不同可能导致结果不一致。这种情况叫竞态条件。辛运的是,大多数线程库都有一些同步源语,以允许线程管理器控制执行和访问。
同步和互斥锁
同步
一般在多线程代码中,总有一些函数或者代码块不希望被多个线程同时执行,如果有两个线程运行的顺序发生变化,就有可能造成代码的执行轨迹或行为不同,产生不一样的数据。这时候就需要使用同步了。
同步可以理解为协同步调,按预定的先后次序进行运行
,比如你先说,我再讲。
示例1:多个线程对全局变量修改的bug
from threading import Thread
import time
num = 0
def work1():
global num
for i in range(1000000):
num += 1
print("-work1-num:%d"%num)
def work2():
global num
for i in range(1000000):
num += 1
print("-work2-num:%d"%num)
t1 = Thread(target=work1)
t1.start()
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()
运行结果:
-work1-num:1105962
-work2-num:1150358
这个程序是两个线程同时对全局变量num进行相加操作,但是因为多线程中线程的执行顺序是不同的,因此出现最后相加结果不是2000000的结果。
示例2:避免全局变量被修改的方法
避免上面的情况可以有很多种方法,第一种是将上面time.sleep(3)的注释去掉,就是在3秒内让线程1执行,3s内执行完毕再执行线程2对num变量进行自增。(不过这种方法跟单线程没区别,也就没有意义去创建多线程了...)
第二种就是使用轮询,代码示例如下:
from threading import Thread
import time
num = 0
item = 1
def work1():
global num
global item
if item == 1:
for i in range(1000000):
num += 1
item = 0
print("-work1-num:%d"%num)
def work2():
global num
while True:# 轮询,一直查看条件是否满足,线程2一直在执行...
if item != 1:
for i in range(1000000):
num += 1
break
print("-work2-num:%d"%num)
t1 = Thread(target=work1)
t1.start()
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()
运行结果:
-work1-num:1000000
-work2-num:200000
这次结果就正确的了,不过这种方法效率也不高。第三种方法就是锁了。
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块定义了Lock类,可以很方便地进行锁定。
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([blocking])
#释放
mutex.release()
其中,锁定方法acquire可以有一个blocking参数。
- 如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为止(如果没有指定,那么默认为True)
- 如果设定blocking为False,则当前线程不会堵
示例:
from threading import Thread,Lock
import time
num = 0
def work1():
global num
# 上锁
mutex.acquire()
for i in range(1000000):
num += 1
# 解锁
mutex.release()
print("-work1-num:%d"%num)
def work2():
global num
mutex.acquire()
for i in range(1000000):
num += 1
mutex.release()
print("-work2-num:%d"%num)
# 创建一把锁,这个锁默认是没有上锁的
mutex = Lock()
t1 = Thread(target=work1)
t1.start()
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()
运行结果:
-work1-num:1000000
-work2-num:2000000
代码中定义了一把锁mutex,线程t1和线程t2都互相竞争着这把锁,谁先上锁,另一方就上不了锁而堵塞。当上锁的线程执行完毕进行解锁,堵塞的线程就争夺到上锁权而进行代码块的运行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。