线程 (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. 如何解决android 通知栏不显示的问题

    android 8.0 以后的版本,在创建通知栏的时候,加了一个channelId的东西.要想在上述版本中显示通知,总共分两步 1.创建Channel if (Build.VERSION.SDK_IN ...

  2. WPF 获取系统 DPI 的多种方法

    原文:WPF 获取系统 DPI 的多种方法 WPF 获取系统 DPI 的多种方法 由于 WPF 的尺寸单位和系统的 DPI 相关,我们有时需要获取 DPI 值来进行一些界面布局的调整,本文汇总了一些 ...

  3. 反射(hasattr和getattr和setattr和delattr)

    目录 一.反射在类中的使用 1.1 应用 二.反射在模块中的使用 2.1 前言 2.2 反射机制 2.2.1 getattr() 2.2.2 hasattr(object, name) 2.2.3 s ...

  4. 怎么在ubuntu下安装使用pycharm

    1.安装jdk 先下载jdk: https://pan.baidu.com/s/1o7MqvKA 解压到本地: 方法一:直接点击右键,点“提取此文件 方法二:使用命令行sudo tar -zxvf j ...

  5. openpyxl常用API

    worksheet.cell(self, row, column, value=None)描述:给指定位置的单元格赋值参数: row&column:必须参数,单元格的坐标 value:可选参数 ...

  6. 英语阅读——Speaking Chinese in America

    这篇文章是<新视野大学英语>第四册的第五单元的文章,第一遍英语阅读完后对比中文,发现自己对作者的观点理解有些出入.作者反对的是认为中国说话客套而美国人直接的观点,利用自己的经历表达了中文也 ...

  7. 面试官,我会写二分查找法!对,没有 bug 的那种!

    前言科普 第一篇二分搜索论文是 1946 年发表,然而第一个没有 bug 的二分查找法却是在 1962 年才出现,中间用了 16 年的时间. 2019 年的你,在面试的过程中能手写出没有 bug 的二 ...

  8. git报错:failed to push some refs to 'git@github.com:JiangXiaoLiang1988/CustomerHandl

    一.错误信息 今天在使用git将代码上传到GitHub的时候报下面的错误: 以前上传代码的时候重来没有出现这种错误,在网上查找了半天终于找到原因了:github中的README.md文件不在本地代码目 ...

  9. VUE基础实用技巧

    Vue以前听说过,有了解过一点.当时还在热衷于原生JavaScript去写一些方法的封装,不是为啥,就感觉这样很帅,后面多多少少接触了一些JQuery的用法,到现在为止,JavaScript原生封装的 ...

  10. Windows下 gcc/g++的安装与配置

    引言 我们知道开发最好用Mac/Linux,效率很高,但是对于很多还是Windows用户的我们来说,编写代码再到linux上运行也是很常有的事情,但对于我们写一些小demo使用上面的流程难免有点兴师动 ...