目录
0. 前言
这篇文章我是根据【莫烦Python】Threading 学会多线程 Python的视频做的学习记录,后来又根据自己的学习进行了一定的补充。莫烦Python好帅好可爱哈哈哈。
关于线程的概念,在这里不做说明,如果想更多了解线程可以看文章末的参考链接。在学操作系统的时候,线程的描述是“CPU资源分配的基本单位”(在这里又忍不住再背以下哈哈),前段时间学了很多的用C的线程编程,所以对线程的理解比较清晰。
线程的使用是可以实现并发(线程执行是有全局解释器锁(GIL)控制,它来保证同时只有一个线程在运行,也就是说多线程并不是真正意义上的同时执行),用线程实现并发的作用即是节省I/O操作程序运行的时间,线程的创建、删除是线程中的要点之一;另外,由于线程的特性,非常适合数据的通信,因此在线程中对共享资源的管理就设计到锁的概念,目前知道的是互斥锁、队列等方式。在本篇博客就从Python怎样用线程实现并发和实现锁这两个点进行整理和介绍(实现锁的部分并没有实际使用到所以还有点粗糙),用到的是threading
库,不使用_thread
(threading比_thread功能更强大)。
1. 实现并发
1.1 创建线程
- 通过创建Thread实例使用线程
import threading def thread_job(): print('This is an added Thread, number id %s' % threading.current_thread()) def main(): added_thread = threading.Thread(target=thread_job) # 创建线程 added_thread.start() # 启动线程 if __name__ == '__main__': main()
- 通过继承Thread类使用线程
import threading import time class my_thread_class(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): ''' 重写Thread类里面的run方法 里面写入你需要执行的代码 之后使用start()方法就可以调用run ''' pass my_thread = my_thread_class() my_thread.start() # 执行线程
- threading的其他方法(method)
threading.currentThread() # 返回当前的线程变量。 threading.enumerate() # 返回一个包含正在运行的线程的list。。 threading.activeCount() # 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
1.2 线程合并——join功能
在主程序中阻塞等待线程结束再继续执行。
import threading
import time
def thread_job():
print('T1 start')
for i in range(10):
time.sleep(0.1)
print('T1 finish')
def t2_job():
print('T2 start')
print('T2 finish')
def main():
# 创建一个名字为T1,运行代码为thread_job的线程
added_thread = threading.Thread(target=thread_job, name='T1')
# 创建运行代码为t2_job的线程
thread2 = threading.Thread(target=t2_job)
added_thread.start()
thread2.start()
# 合并运行代码为thread_job的线程
added_thread.join()
# 合并运行代码为t2_job的线程
thread2.join()
print('all down!')
if __name__ == '__main__':
main()
1.3 全局解释器锁(GIL)
这个只是顶层实现线程的一个概念,实际并不要编程处理。
CPython 解释器本身就不是线程安全的,因此有全局解释器锁(GIL),一次只允许使用一个线程执行Python 字节码。因此,一个Python 进程通常不能同时使用多个CPU 核心。
编写Python 代码时无法控制GIL;不过执行耗时的任务时,可以使用一个内置的函数或一个使用C 语言编写的扩展释放GIL。其实,有个使用C 语言编写的Python 库能管理GIL,自行启动操作系统线程,利用全部可用的CPU 核心。这样做会极大地增加库代码的复杂度,因此大多数库的作者都不这么做。
然而,标准库中所有执行阻塞型I/O 操作的函数,在等待操作系统返回结果时都会释放GIL。这意味着在Python 语言这个层次上可以使用多线程,而I/O 密集型Python 程序能从中受益:一个Python 线程等待网络响应时,阻塞型I/O 函数会释放GIL,再运行一个线程。
因此David Beazley 才说:“Python 线程毫无作用。”
Python 标准库中的所有阻塞型I/O 函数都会释放GIL,允许其他线程运行。
time.sleep() 函数也会释放GIL。因此,尽管有GIL,Python 线程还是能在
I/O 密集型应用中发挥作用。
——《流畅的python》17.2
2. 实现锁
2.1 Queue功能——值传递
可以往线程里面传递参数,但是线程不能有返回值。
以下实例的目的是将data列表中的值全部平方:
import threading
import time
from queue import Queue
# 需要修改的列表数据
data = [[1, 2, 3], [3, 4, 5], [4, 4, 4], [5, 5, 5]]
# 定义一个功能函数,将列表中的值平方
def job(l):
for i in range(len(l)):
l[i] = l[i]**2
return l
# 如果不用多线程,用以下方式实现
def no_threading():
result = []
for d in data:
r = job(d)
result.append(r)
print(result)
def job2(l, q):
for i in range(len(l)):
l[i] = l[i]**2
q.put(l)
def multithreading():
q = Queue()
threads = []
# 启动线程
for i in range(4):
t = threading.Thread(target=job2, args=(data[i], q))
t.start()
threads.append(t)
# 回收线程
for thread in threads:
thread.join()
# 获取数据
results = []
for _ in range(4):
results.append(q.get())
print(results)
if __name__ == '__main__':
# no_threading()
multithreading()
2.2 线程锁
import threading
def job1():
global A, lock
lock.acquire()
for i in range(10):
A += 1
print('job1', A)
lock.release()
def job2():
global A, lock
lock.acquire()
for i in range(10):
A += 10
print('job2', A)
lock.release()
if __name__ == '__main__':
A = 0
lock = threading.Lock()
t1 = threading.Thread(target=job1)
t2 = threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
3. threading函数统计表
函数名 | 功能 |
---|---|
threading.Thread() | 创建线程 |
threading.active_count() | 目前有多少个激活的线程 |
threading.enumerate() | 激活的线程是哪几个 |
threading.current_thread() | 正在运行的是哪个线程 |
4. 参考链接
以下参考链接阅读的复杂性依次提高:
Python多线程库threading的使用
Python threading实现多线程 基础篇
Python内置库:threading(多线程)
threading — 基于线程的并行