【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
python线程
一、什么是线程
进程是资源分配的最小单位,线程则是CPU调度的最小单位。
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
特点:
- 易于调度。
- 提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
- 开销少。创建线程比创建进程要快,所需开销很少。
- 利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。
二、GIL(Global Interpreter Lock)全局解释器锁
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
-
设置GIL
-
切换到一个线程去运行
-
运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
-
把线程设置为睡眠状态
-
解锁GIL
-
再次重复以上所有步骤
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
三、线程的生命周期
- New新建 :新创建的线程经过初始化后,进入Runnable状态。
- Runnable就绪:等待线程调度。调度后进入运行状态。
- Running运行:线程正常运行
- Blocked阻塞:暂停运行,解除阻塞后进入Runnable状态重新等待调度。
- Dead消亡:线程方法执行完毕返回或者异常终止
四、Python threading模块
python提供了两个标准库用于多线程编程,thread模块提供了基本的线程和锁的支持,而 threading 提供了更高级别,功能更强的线程管理的功能。一般都建议使用threading模块,毕竟功能更强大,更好管理。
Thread对象的属性
- name 线程名
- ident 线程的标识符
- daemon 布尔标志,表示这个线程是否是守护线程
Thread对象的方法
- init(group=None, tatget=None, name=None, args=(), kwargs ={}, verbose=None, daemon=None) 实例化一个线程对象,需要有一个可调用的 target,以及其参数 args 或 kwargs。还可以传递 name 或 group 参数,不过后者还未实现。此 外 , verbose 标 志 也 是 可 接 受 的 。 而 daemon 的 值 将 会 设 定 thread.daemon 属性/标志
- start() 开始执行该线程
- run() 定义线程功能的方法(通常在子类中被应用开发者重写)
- join (timeout=None) 直至启动的线程终止之前一直挂起;除非给出了 timeout(秒),否则 会一直阻塞
- is_alive() 布尔标志,表示这个线程是否还存活
threading模块其他函数
- start() 开始执行该线程
- active_count() 当前活动的 Thread 对象个数
- enumerate() 返回当前活动的 Thread 对象列表
- settrace(func) 为所有线程设置一个 trace 函数
- setprofile (func) 为所有线程设置一个 profile 函数
- stack_size(size=0) 返回新创建线程的栈大小;或为后续创建的线程设定栈的大小 为 size
- Lock() 加载线程的锁对象,是一个基本的锁对象,一次只能一个锁定,其余锁请求,需等待锁释放后才能获取,对象有acquire()和release()方法
- RLock() 多重锁,在同一线程中可用被多次acquire。如果使用RLock,那么acquire和release必须成对出现,调用了n次acquire锁请求,则必须调用n次的release才能在线程中释放锁对象
threading 提供了两种调用方式:
- 直接调用
import threading
def func(n): # 定义每个线程要运行的函数
while n > 0:
print(u"当前线程数:", threading.activeCount())
n -= 1
for x in range(2):
t = threading.Thread(target=func, args=(2,)) # 生成一个线程实例,生成实例后 并不会启动,需要使用start命令
t.start() # 启动线程
- 继承式调用
import threading
class MyThread(threading.Thread): # 继承threading的Thread类
def __init__(self, num):
threading.Thread.__init__(self) # 必须执行父类的构造方法
self.num = num # 传入参数 num
def run(self): # 定义每个线程要运行的函数
while self.num > 0:
print("当前线程数:", threading.activeCount())
self.num -= 1
for x in range(5):
t = MyThread(2) # 生成实例,传入参数
t.start() #启动线程
子线程阻塞
join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的join函数。
import threading
def func(n):
while n > 0:
print("当前线程数:{}".format(threading.activeCount()) )
n -= 1
threads = [] #运行的线程列表
for x in range(5):
t = threading.Thread(target=func, args=(2,))
threads.append(t) # 将子线程追加到列表
t.start()
for t in threads:
t.join()
print("主线程:{}".format(threading.current_thread().name))
五、线程锁(互斥锁)
通过 threading.Lock() 我们可以申请一个锁。然后 acquire 方法进入临界区.操作完共享数据 使用 release 方法退出.
def func():
global num
lock.acquire() # 加锁
num = num - 1
lock.release() # 解锁
print(num)
threads = []
lock = threading.Lock() #生成全局锁
for x in range(10):
t = threading.Thread(target=func)
threads.append(t)
t.start()
for t in threads:
t.join()
六、信号量
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
import threading,time
num = 10
def func():
global num
lock.acquire()
time.sleep(2)
num = num - 1
lock.release()
print(num)
threads = []
lock = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
for x in range(10):
t = threading.Thread(target=func)
threads.append(t)
t.start()
for t in threads:
t.join()
print("主线程:{}".format(threading.current_thread().name))
来源:oschina
链接:https://my.oschina.net/u/3696975/blog/3142739