您当前的位置: 首页 >  Python

简单明了的 Python 多线程来了 | 原力计划

CSDN 程序人生 发布时间:2020-06-14 10:00:00 ,浏览量:3

作者 | 万里羊

责编 | 王晓曼

出品 | CSDN博客

线程和进程

 

计算机的核心是CPU,它承担了所有的计算任务,就像是一座工厂在时刻运行。

如果工厂的资源有限,一次只能供一个车间来使用,也就是说当一个车间开工时其它车间不能工作,也就是一个CPU一次只能执行一个任务。

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

当然一个车间还有很多工人,他们互相协同完成一个工作;而线程就好比工厂的工人,一个进程可以包含多个线程。

线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

多线程与多进程

 

通俗易懂的理解就是:

多进程:允许多个任务同时进行
多线程:允许单个任务分成不同的部分运行

Python多线程的实现

Python3 通过两个标准库 thread(python2中是thread模块)和 threading 提供对线程的支持。

thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading:

import threading #导入threading库
import time

def run(n):
    print("task", n)
    time.sleep(1) #延时一秒
    print('2s')
    time.sleep(1)
    print('1s')
    time.sleep(1)
    print('0s')
    time.sleep(1)

if __name__ == '__main__':
    t1 = threading.Thread(target=run, args=("t1",))#创建线程1,取名为t1
    t2 = threading.Thread(target=run, args=("t2",))#创建线程2,取名为t2
    t1.start() #开启线程t1
    t2.start() #开启线程t2

输出结果:

task t1
task t2
2s
2s
1s
1s
0s
0s

可以看出先开启了线程t1,在开启t2然后每隔一秒打印数据。

自定义线程

 

通过继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法:

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数必须要写
        self.n = n

    def run(self):
        print("task", self.n)
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
        time.sleep(1)
        print('0s')
        time.sleep(1)

if __name__ == "__main__":
    t1 = MyThread("t1")
    t2 = MyThread("t2")
    t1.start()
    t2.start()

输出结果:

task t1
task t2
2s
2s
1s
1s
0s
0s

守护线程 

下面这个例子,使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。

import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)       #此时子线程停1s
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)   #把子进程设置为守护线程,必须在start()之前设置
    t.start()
    print("end")

输出结果:

task t1
end

可以看到,t1线程并没有执行完毕,而是直接结束了。说明设置子线程为守护线程之后,主线程结束了,子线程也立即结束不再执行。

程序中不是只创建了一个线程么?怎么会有主线程和子线程呢?

其实呢程序运行时就会创建一个线程,而这个线程就是主线程。

主线程等待子线程运行结束

import threading
import time

def run(n):
    print("task", n)
    time.sleep(1)      
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')

if __name__ == '__main__':
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)   #把子进程设置为守护线程,必须在start()之前设置
    t.start()
    t.join() # 设置主线程等待子线程结束
    print("end")

输出结果:

task t1
3
2
1
end

运行.join()后的程序表明等待所有线程结束以后再进行.join()之后的操作结合以上代码就是,等待t1结束以后再执行end。

多线程共享全局变量

 

线程是进程的执行单元,进程是系统分配资源的最小单位,所以在同一个进程中的多线程是共享资源的。那么共享资源时就需要用到全局变量。

import threading
import time

num = 100

def work1():
    global num
    for i in range(3):
        num += 1
    print("in work1 num is : %d" % num)

def work2():
    global num
    print("in work2 num is : %d" % num)

if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t1.start()
    time.sleep(1)
    t2 = threading.Thread(target=work2)
    t2.start()

运行结果如下:

in work1 num is : 103
in work2 num is : 103

可以看到两者输出的结果是相同的,说明是可以共享全局变量的。

互斥锁

由于线程之间是进行随机调度,并且每个线程可能只执行n条,当多个线程同时修改同一条数据时可能会出现脏数据,因而,出现了线程锁,即同一时刻只允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁, 在下面的实例中, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。

由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。

为了方式上面情况的发生,就出现了互斥锁(Lock):

import threading

def work1():
    global A,lock#定义A和lock为全局变量
    lock.acquire()#上锁
    for i in range(5):
        A+=1
        print('work1',A)
    lock.release()#解锁
def work2():
    global A,lock
    lock.acquire()
    for i in range(5):
        A+=10
        print('work2',A)
    lock.release()
if __name__=='__main__':
    lock=threading.Lock()#定义锁
    A=0
    t1=threading.Thread(target=work1)
    t2=threading.Thread(target=work2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

输出结果:

work1 1
work1 2
work1 3
work1 4
work1 5
work2 15
work2 25
work2 35
work2 45
work2 55

可以发现对两组数据是没有影响的,感兴趣的可以尝试一下不加锁会有什么情况。

递归锁

RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLcok类。

import threading
import time

def Func(lock):
    global gl_num
    lock.acquire()
    gl_num += 1
    time.sleep(1)
    print(gl_num)
    lock.release()

if __name__ == '__main__':
    gl_num = 0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=Func, args=(lock,))
        t.start()

输出结果:

1
2
3
4
5
6
7
8
9
10

信号量(BoundedSemaphore类)

 

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

实际中博主还没有用到过,所以理解不是特别透彻。

import threading
import time

def run(n, semaphore):
    semaphore.acquire()   #加锁
    time.sleep(1)
    print("run the thread:%s\n" % n)
    semaphore.release()     #释放

if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(22):
        t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
        t.start()
    while threading.active_count() != 1:
        pass  # print threading.active_count()
    else:
        print('-----all threads done-----')

输出结果有点长,就不贴输出结果了。

事件(Event类)

python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:

  • clear 将flag设置为“False”;

  • set 将flag设置为“True”;

  • is_set 判断是否设置了flag;

  • wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态。

事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞:

import threading
import time
event = threading.Event()
def lighter():
    count = 0
    event.set()     #初始值为绿灯
    while True:
        if 5             
关注
打赏
1688896170
查看更多评论
0.1133s