本文参照文章:python多线程实现 在理解的基础上手敲了一遍原文中的代码并运行。将局部内容做了些许修改使阅读起来更通顺。感谢原作者,用简易平实的描述讲清楚了多线程的基本原理和使用!

1.threading模块

# !/usr/bin/env python
# -*- coding: gbk -*-

import threading
import time

def test(name):
    print("线程运行",name)
    time.sleep(1)
    print("线程结束",name)

if __name__ == "__main__":
    t1 = threading.Thread(target=test,args = ("t1",))
    t2 = threading.Thread(target=test,args = ("t2",))

    t1.start()
    t2.start()

运行结果:

python 多线程 摄像 处理图片 python多线程教程_python多线程的用法

我们线程运行是t1,t2顺序,但是运行结束会出现t2,t1的顺序,原因是我们线程调度是由操作系统层面来进行操作的,它的资源分配是由OS来执行的,实际上它的底层是一种GIL锁资源抢占的方式。这个锁资源抢占不太懂,以后有机会可以细究。

2.自定义线程

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

# !/usr/bin/env python
# -*- coding: gbk -*-

import threading
import time

class MyThread(threading.Thread):
    def __init__(self,n):
        super(MyThread,self).__init__()
        self.n = n

    def run(self):
        print("task run:",self.n)
        time.sleep(2)
        print("task finish:",self.n)

if __name__ == "__main__":
    t1 = MyThread(n="t1")
    t2 = MyThread(n="t2")

    t1.start()
    t2.start()

这个自定义线程我们一般用不到,除非你是需要在人家的基础上多增加一些实现的方法才用它。

3.守护线程

使用setDaemon(Ture)把所有的子线程都变成主线程的守护线程,当主线程结束后,子线程也随之结束,所以当主线程结束后,整个程序就退出了。

# !/usr/bin/env python
# -*- coding: gbk -*-

import threading
import time

class MyThread(threading.Thread):
    def __init__(self,n):
        super(MyThread,self).__init__()
        self.n = n

    def run(self):
        print("task run:",self.n)
        time.sleep(2)
        print("task finish:",self.n)

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

运行结果:

python 多线程 摄像 处理图片 python多线程教程_主线程_02

结果我们发现和我们预想的不一样,只有run,却没有finish,因为设置了守护线程,所以主线程结束程序就结束了。
那我们如果要等子线程彻底运行完的话就要使用join方法,这个就是等待的意思,等待子线程全部运行结束再停止

if __name__ == "__main__":
    t1 = MyThread(n="t1")
    t2 = MyThread(n="t2")
    t1.setDaemon(True)
    t2.setDaemon(True)

    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("主线程运行结束")

python 多线程 摄像 处理图片 python多线程教程_python_03

4.全局变量

# !/usr/bin/env python
# -*- coding: gbk -*-

import threading
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num = g_num + 1
    print(g_num)

def work2():
    global g_num
    print(g_num)

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

程序中只是多了一个global,来声明一下这个g_num共享,如果没有这个global的话,程序就会错误,因为在程序觉得在子程序中找不到g_num这个变量
运行结果:

python 多线程 摄像 处理图片 python多线程教程_python多线程的用法_04

5.互斥锁

由于线程之间是随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,我的理解就是,当多个线程进行资源抢占的时候,我这个线程在修改变量的时候,其他线程都不能进行修改,也不会把锁放开,只有我把操作全部完成之后,才会把锁放开。接下来我们看两个程序,分别就是用了互斥锁和没用互斥锁,我们比较一下他们的运行结果:
(1)没用互斥锁

# !/usr/bin/env python
# -*- coding: gbk -*-

from threading import Thread,Lock
import os,time

def work():
    global n
    temp = n
    time.sleep(0.1)
    n = temp - 1

if __name__ == "__main__":
    n = 100
    l = []
    for i in range(100):
        p = Thread(target = work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print(n)

运行结果:

python 多线程 摄像 处理图片 python多线程教程_主线程_05

(2)使用互斥锁

# !/usr/bin/env python
# -*- coding: gbk -*-

from threading import Thread,Lock
import os,time

def work():
    global n
    lock.acquire()
    temp = n
    time.sleep(0.1)
    n = temp - 1
    lock.release()

if __name__ == "__main__":
    lock = Lock()
    n = 100
    l = []
    for i in range(100):
        p = Thread(target = work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print(n)

运行结果:

python 多线程 摄像 处理图片 python多线程教程_互斥锁_06

没上锁的运行结果是99,上了锁的运行结果却是0,最终我们想要的结果应该是0,是99的原因是出现了线程安全的问题.

6.信号量

也就是我们通常说的几线程运行,我们设置最多允许几个线程同时运行,只有之前的运行完,把信号量交换回来,它才会继续运行

# !/usr/bin/env python
# -*- coding: gbk -*-

import threading
import os,time

def run(n,semaphore):
    semaphore.acquire()
    time.sleep(1)
    print(f"run the thread:{n}")
    semaphore.release()


if __name__ == "__main__":
    num = 0
    semaphore = threading.BoundedSemaphore(5)
    for i in range(22):
        t = threading.Thread(target=run,args=("t-%s"%i,semaphore))
        t.start()

    while threading.active_count()!=1:
        pass
    else:
        print("-----all threads done-----")

这个程序我们总共线程是21,但是最多同时运行的线程数只有5,因为我们设置的信号量是5。