Python thread (线程)

线程 (thread)

操作系统最小的调度单位,是一串指令的集合
程序一开始就有一个主线程,新启动的线程和主线程之间互不影响,主线程启动子线程之后就相互独立(子线程也可以启动线程),无论子线程是否执行结束主线程都会继续执行,程序在所有线程执行结束后关闭

全局解释器锁 (GIL)

由于无法控制线程执行顺序,为了防止数据出现错误,通过 GIL 使同一时间只有一个线程在工作
需要明确的一点是 GIL 并不是 Python 的特性,它是在实现 Python 解析器 (CPython) 时所引入的一个概念,Python 完全可以不依赖 GIL

threading 模块

启动线程

直接调用

示例:

import threading
import time


def run(i):		# 函数名随意
    print('test', i)
    time.sleep(1)


t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t3 = threading.Thread(target=run, args=('t3',))
t1.start()
t2.start()
t3.start()

threading.Thread(target=run, args=('t1',)) 中 target 为线程执行的函数,args 中为传入的参数

继承式调用

示例:

import threading


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

    def run(self):	# 函数名必须是 run
        print('class test', self.n)


t1 = MyThread('t1')
t2 = MyThread('t2')
t3 = MyThread('t3')
t1.start()
t2.start()
t3.start()

注意:如果需要获得线程执行函数的返回值,可以将返回值放入队列,再从队列中获取 (关于 Python 队列 <- 点击查看)

多线程与单线程区别

IO 操作不占用 CPU,计算占用 CPU
Python 多线程不适合 CPU 密集操作型的任务,适合 IO 操作密集型的任务
单线程示例:

import threading
import time


def run(i):
    print('test', i)
    time.sleep(1)


run('t1')
run('t2')
run('t3')

与多线程对比可以发现:多线程是 print 之后等待 1s 之后结束,而单线程每次 print 之后都要等待。

其他

join

主线程创建子线程之后,主线程就与子线程相互独立,不管子线程是否执行完成,主线程都会继续执行下去
使用 join 可以让主线程等待子线程执行完成之后,再继续执行
示例:

import threading
import time


def run(th):
    print('test', th)
    time.sleep(2)


start_time = time.time()
threading_list = []
num = 0
for i in range(50):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.start()
    threading_list.append(t)
for item in threading_list:
    item.join()
print('totally', time.time() - start_time)

如果不使用 join 主线程在创建子线程之后就会继续执行,直接输出时间。再等待两秒,所有线程执行结束后程序结束
使用 join 后主线程会等待相应子线程全部执行结束之后再输出时间

守护线程(deamon)

守护线程是为主线程服务的,只要非守护线程执行完成程序就会直接结束
当一个子线程被设置为守护线程,程序就不会再等待他执行完成再结束
示例:

import threading
import time


def run(th):
    print('test', th)
    time.sleep(2)


start_time = time.time()
num = 0
for i in range(50):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.setDaemon(True)
    t.start()
print('totally', time.time() - start_time)

注意setDeamon(true) 需要在 start 之前设置

线程锁(互斥锁)

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据。
此时,如果多个线程同时修改同一份数据,就会出错
已经有 GIL 为什么还会出错:
虽然已经有 GIL 保证同一时刻只有一个线程在修改数据,但是当某个进程在获取数据修改,还没保存修改的结果前 release GIL ,这时就会出错

import threading
import time


def run(th):
    lock.acquire()	# 获取锁
    global num
    time.sleep(0.01)
    num += 1
    print('test', th)
    lock.release()  # 释放锁


lock = threading.Lock()
start_time = time.time()
threading_list = []
num = 0
for i in range(50):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.start()
    threading_list.append(t)
for item in threading_list:
    item.join()
print(num)

注意:每个线程执行时间不能过长,否则就变成串行了

死锁

当有多层互斥锁同时存在时会出现死锁,程序进入死循环

import threading


def run1():
    lock.acquire()
    global num1
    num1 += 1
    lock.release()
    return num1


def run2():
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2


def run3():
    lock.acquire()
    res1 = run1()
    res2 = run2()
    lock.release()
    print(res1, res2)


num1, num2 = 0, 0
lock = threading.Lock()
for i in range(10):
    t = threading.Thread(target=run3)
    t.start()

while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('-----finished-----')
    print(num1, num2)

RLock 递归锁

为了避免死锁,就需要使用递归锁 RLock

import threading


def run1():
    lock.acquire()
    global num1
    num1 += 1
    lock.release()
    return num1


def run2():
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2


def run3():
    lock.acquire()
    res1 = run1()
    res2 = run2()
    lock.release()
    print(res1, res2)


num1, num2 = 0, 0
lock = threading.RLock()
for i in range(10):
    t = threading.Thread(target=run3)
    t.start()

while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('-----finished-----')
    print(num1, num2)

Semaphore (信号量)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据

import threading
import time
import sys


def run(th):
    semaphore.acquire()
    string = 'threading:' + str(th) + '\n'
    sys.stdout.write(string)
    time.sleep(2)
    semaphore.release()


semaphore = threading.BoundedSemaphore(5)	# 最多允许5个线程同时运行
for i in range(20):
    t = threading.Thread(target=run, args=(i, ))
    t.start()

while threading.active_count() != 1:
    pass
else:
    print('Done')

从程序的运行过程可以看出:开始有5个线程在运行,这5个线程结束之后又有5个线程启动

Event (事件)

Event 可以让线程间进行交互,与设置全局变量同理
Event 借助internal flag有两种状态:TrueFalse通过setclear改变状态,线程通过is_set()获取 Event 状态,wait()False时会阻塞
红绿灯与汽车交互示例:

import threading
import time


event = threading.Event()


def light():
    count = 0
    event.set()
    while 1:
        if 5 < count < 10:
            event.clear()
            print('red')
        elif count == 10:
            event.set()
            print('green')
            count = 0
        else:
            print('green')
        count += 1
        time.sleep(1)


def car():
    while 1:
        if event.is_set():
            print('running...')
            time.sleep(1)
        else:
            print('waiting')
            event.wait()


l1 = threading.Thread(target=light,)
l1.start()
c1 = threading.Thread(target=car,)
c1.start()

posted on 2019-07-03 15:56  doubtful  阅读(1383)  评论(0编辑  收藏  举报

导航