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

最新文章

  1. Django模版语言 格式化显示 后台datatime()时间
  2. BZOJ 2038: [2009国家集训队]小Z的袜子(hose)
  3. 我的c++学习(12)指针作为函数参数
  4. Java printf
  5. c++获取系统时间(引用别人的博文)
  6. maven 仓库搜索添加需要的jar包
  7. 将Html文档整理为规范XML文档
  8. struct stat结构体的详解和用法
  9. java多线程总结一:线程的两种创建方式及优劣比较
  10. My.Ioc 代码示例——谈一谈如何实现装饰器模式,兼谈如何扩展 My.Ioc
  11. ISO14443标准详细介绍
  12. php 学习路线 赵兴壮2014年4月28 日 加油
  13. 【结构型】Composite模式
  14. Codeforces 582B Once Again
  15. 更改pip源至国内镜像,显著提升下载速度
  16. UI设计切忌墨守成规,但改变也须用数据说话
  17. postgresql分析函数
  18. 两种语言实现设计模式(C++和Java)(三:策略模式)
  19. mysql 取年、月、日、时间
  20. Python使用闭包结合配置自动生成函数

热门文章

  1. NVIDIA-SMI has failed because it couldn&#39;t communicate with the NVIDIA driver ...
  2. 2019-2020-1 20199305《Linux内核原理与分析》第二周作业
  3. angularjs事件通信$on,$emit,$broadcast详解
  4. 我说精通字符串,面试官竟然问我 Java 中的 String 有没有长度限制?
  5. MongoDB for OPS 03:分片 shard 集群
  6. Python爬虫反反爬:CSS反爬加密彻底破解!
  7. VMWare虚拟机应用介绍
  8. Vue-router中的history模式和hash模式
  9. cmdb项目-3
  10. RPA之AA