python线程

不问归期 提交于 2019-12-13 17:19:22

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

python线程


一、什么是线程

进程是资源分配的最小单位,线程则是CPU调度的最小单位。

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

特点:

  • 易于调度。
  • 提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
  • 开销少。创建线程比创建进程要快,所需开销很少。
  • 利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。

二、GIL(Global Interpreter Lock)全局解释器锁

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python 虚拟机按以下方式执行:

  1. 设置GIL

  2. 切换到一个线程去运行

  3. 运行:

    a. 指定数量的字节码指令,或者

    b. 线程主动让出控制(可以调用time.sleep(0))

  4. 把线程设置为睡眠状态

  5. 解锁GIL

  6. 再次重复以上所有步骤

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

三、线程的生命周期

  1. New新建 :新创建的线程经过初始化后,进入Runnable状态。
  2. Runnable就绪:等待线程调度。调度后进入运行状态。
  3. Running运行:线程正常运行
  4. Blocked阻塞:暂停运行,解除阻塞后进入Runnable状态重新等待调度。
  5. 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))

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!