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都互相竞争着这把锁,谁先上锁,另一方就上不了锁而堵塞。当上锁的线程执行完毕进行解锁,堵塞的线程就争夺到上锁权而进行代码块的运行。


syushin
948 声望316 粉丝