线程 (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()

Python thread (线程)的更多相关文章

  1. python的线程thread笔记

    python的线程是用thread和threading来实现的.其中利用threading会更好,因为thread没有线程保护,当主线程退出了之后,子线程也会被强行退出.threading支持守护线程 ...

  2. Python之线程、进程和协程

    python之线程.进程和协程 目录: 引言 一.线程 1.1 普通的多线程 1.2 自定义线程类 1.3 线程锁 1.3.1 未使用锁 1.3.2 普通锁Lock和RLock 1.3.3 信号量(S ...

  3. TLS 与 python thread local

    TLS 先说TLS( Thread Local Storage),wiki上是这么解释的: Thread-local storage (TLS) is a computer programming m ...

  4. 在python中单线程,多线程,多进程对CPU的利用率实测以及GIL原理分析

    首先关于在python中单线程,多线程,多进程对cpu的利用率实测如下: 单线程,多线程,多进程测试代码使用死循环. 1)单线程: 2)多线程: 3)多进程: 查看cpu使用效率: 开始观察分别执行时 ...

  5. python中线程和进程(一)

    目录 进程和线程 Python中的线程 1. Thread类 2. 线程的启动 3. 线程的传参 4. 线程的属性和方法 5. daemon线程和non-daemon线程 6. join方法 7. 定 ...

  6. 一文了解Python的线程

    问题 什么是线程? 如何创建.执行线程? 如何使用线程池ThreadPoolExecutor? 如何避免资源竞争问题? 如何使用Python中线程模块threading提供的常用工具? 目录 1. 什 ...

  7. <python的线程与threading模块>

    <python的线程与threading模块> 一 线程的两种调用方式 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而thre ...

  8. Python之线程 2 - Python实现线程

    一 python与线程 1.全局解释器锁GIL(用一下threading模块之后再来看~~) 2.python线程模块的选择 二 Threading模块 1.线程创建 2.多线程与多进程 3.多线程实 ...

  9. python之线程相关操作

    1.线程: 一个进程可以有多个线程,共享一个进程的资源: 2.进程线程的区别:  进程是资源分配的最小单位,线程是程序执行的最小单位 3.python中线程模块threading, 提供的类: Thr ...

随机推荐

  1. 201777010217-金云馨《面向对象程序设计(Java)》第二周学习总结

    项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p ...

  2. acwing 60. 礼物的最大价值

    地址 https://www.acwing.com/problem/content/56/ 在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0). 你可以从棋盘的左上角开始拿 ...

  3. LeetCode 5275. 找出井字棋的获胜者 Find Winner on a Tic Tac Toe Game

    地址 https://www.acwing.com/solution/LeetCode/content/6670/ 题目描述A 和 B 在一个 3 x 3 的网格上玩井字棋. 井字棋游戏的规则如下: ...

  4. 【Oracle】常用函数

    来源自:https://www.cnblogs.com/lxl57610/p/7442130.html Oracle SQL 提供了用于执行特定操作的专用函数.这些函数大大增强了 SQL 语言的功能. ...

  5. 【python爬虫】正则表达式

    一.数据的分类 1.结构化数据 特点:数据以行为单位,每一个数据表示一个实体.每一行数据的属性都是一样的. 举例:关系型数据库中的表就是结构化数据. 处理方法:sql 2.半结构化数据 特点:结构化数 ...

  6. 错题shell

    1.判断/root/class21/inittab.txt文件是否大于100行,如果大于,则显示”inittab is a big file.”否者显示”inittab is a small file ...

  7. vscode源码分析【五】事件分发机制

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...

  8. python--8大排序(原理+代码)

    常用的排序方法:冒泡排序.选择排序.插入排序.快速排序.堆排序.归并排序 冒泡排序(Bubble Sort): 比较相邻的元素.如果第一个比第二个大(升序),就交换他们两个. 对每一对相邻元素作同样的 ...

  9. C语言程序设计100例之(7):级数求和

    例7    级数求和 题目描述 已知: Sn =1+1/2+1/3+…+1/n.显然对于任意一个整数 k,当 n 足够大的时候,Sn>k. 现给出一个整数 k,要求计算出一个最小的 n,使得 S ...

  10. Groovy元编程简明教程

    同函数式编程类似,元编程,看上去像一门独派武学. 在 <Ruby元编程>一书中,定义:元编程是运行时操作语言构件的编程能力.其中,语言构件指模块.类.方法.变量等.常用的主要是动态创建和访 ...