Python之线程threading

1 threading模块

threading模块是在低级别_thread模块上构建的的高级别线程接口.继承_thread功能,易用性更强.
_thread模块提供处理多进程(也称轻量级继承或任务)的基本单元,多进程控制特点是共享全局数据空间.简单锁(也称互斥或二进制信号量)可实现进程同步.
python线程属于内核级别,即由操作系统调度(如单线程一旦遇到IO就会被迫交出CPU执行权限,切换到其他线程运行).

2 方法

from threading import *
active_count()import threading
threading.active_count()
序号 方法 描述
1 active_count() 返回Thread对象当前活动的进程数,返回数量与方法enumerate()返回的列表长度一致
2 current_thread() 返回当前Thread对象相对应控制的线程
3 main_thread() 返回主线程对象,一般主线程是从Python开始解释(运行)的线程
4 TIMEOUT_MAX 阻塞最长等待时间
5 enumerate 返回当前线程对象所有活动的线程列表,列表包括后台进程,current_thread()建立的虚拟线程对象和主线程,不包括结束的线程未启动的线程

3 Python程序默认线程

  • Demo
import threading

counts = threading.active_count()
enu = threading.enumerate()
print("Default thread counts: {}".format(counts))
print("Default thread : {}".format(enu))
print("xindaqi")
  • Result
Default thread counts: 5
Default thread : [<_MainThread(MainThread, started 139949210203968)>, , , , ]
xindaqi
  • Analysis
    一个默认Python程序有5个线程,分别为主线程(MainThread),线程(Thread),心跳线程(Heartbeat), 历史保存线程(HistorySavingThread),Unix父类轮巡线程(ParentPollerUnix)

4 Thread类

线程类表示单独控制运行的线程活动.有两种建立线程的方式:

  • 直接使用Thread类;
  • 重构run()方法.

一旦建立线程对象,需使用start()方法启动线程,这将在独立控制的线程中调用run().
线程启动后,该线程被标记为’alive’,当run()方法终止即结束线程,或抛出错误也会结束线程.线程可设置标志位作为"守护进程",该标志位表示该线程在Pyhton程序运行的整个声明周期,在守护进程退出时结束.

4.1 Thread参数

Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

序号 方法 描述
1 group None,未来扩充线程分组时使用
2 target 被run()方法调用的方法,默认None,无可执行函数
3 name 线程名称,默认为Thread-N,N为小十进制数
4 args tuple类型的参数,默认为()
5 kwargs dict类型的参数,默认为{}
6 daemon 线程是否守护进程的标志位,默认为None,后台属性继承当前线程
7 重写run 在初始化类时,引用基类结构 Thread.__init __ ()

4.2 Thread类方法

序号 方法 描述
1 start() 启动线程活动
2 run() 表示线程活动的方法,可在子类中重写该方法
3 join() 等待线程结束
4 name 仅用于标识的字符串
5 daemon 布尔值,表示线程是否为后台线程,在start()前使用,默认False,当没有活动的前台进程(非后台)进程时,Python程序退出,即通过Thread对象建立的前台进程执行全部结束,程序才退出,若主程序不结束,前台进程会一直运行,需要手动结束;设置为后台进程(daemon=True)时,主程序结束后,这些后台进程会自动结束,无需手动停止.

4.3 新建线程

4.3.1 直接使用Thread类

  • Demo
import threading
import time

def action(arg):
    time.sleep(1)
    print("Current thread: {}".format(threading.currentThread().getName()))
    print("Threading No.:{}".format(arg))
    
for i in range(3):
    
    t = threading.Thread(target=action, name="xdq thread {}".format(i), args=(i, ))
    t.start()
    enu = threading.enumerate()
    count = threading.active_count()
    print("Threading counts: {}".format(count))
print("Main thread end!")
  • Result
Threading counts: 6
Threading counts: 7
Threading counts: 8
Main thread end!
Current thread: xdq thread 0
Threading No.:0
Current thread: xdq thread 1
Threading No.:1
Current thread: xdq thread 2
Threading No.:2

4.3.2 重构run()方法建立线程

  • Demo
import threading
import time

def action(arg):
    time.sleep(1)
    print("Current thread: {}".format(threading.currentThread().getName()))
    print("Threading No.:{}".format(arg))
    
for i in range(3):
    t = threading.Thread(target=action, name="xdq thread {}".format(i), args=(i, ), daemon=False)
    t.start()
    enu = threading.enumerate()
    count = threading.active_count()
    print("Threading counts: {}".format(count))

print("Main thread end!")
  • Result
Threading counts: 6
Threading counts: 7
Threading counts: 8
Main thread end!
Current thread: xdq thread 0
Threading No.:0
Current thread: xdq thread 1
Threading No.:1
Current thread: xdq thread 2
Threading No.:2

4.4 进程模式daemon

4.4.1 daemon=False

import threading
import time

def action(arg):
    time.sleep(1)
    # 获取当前线程
    print("Current thread: {}".format(threading.currentThread().getName()))
    print("Threading No.:{}".format(arg))
    
for i in range(3):
    # 新建线程
    t = threading.Thread(target=action, name="xdq thread {}".format(i), args=(i, ))
    # 获取线程状态
    daemon_t = t.isDaemon()
    print("Daemon value: {}".format(daemon_t))
    # 开启线程
    t.start()
    # 获取活动线程列表
    enu = threading.enumerate()
    # 获取活动线程数量
    count = threading.active_count()
    print("Threading counts: {}".format(count))
    print("Enumerate thread: {}".format(enu))
# 主线程
print("Main thread end!")
  • Result
Threading counts: 6
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , ]
Threading counts: 7
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , , ]
Threading counts: 8
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , , , ]
Main thread end!
Threading No.:0
Threading No.:1
Threading No.:2

此时线程并没有自动结束:


Python之线程threading_第1张图片

图4.1 daemon=False运行结果

4.4.2 daemon=True

  • Demo
import threading
import time

def action(arg):
    time.sleep(1)
    # 获取当前线程
    print("Current thread: {}".format(threading.currentThread().getName()))
    print("Threading No.:{}".format(arg))
    
for i in range(3):
    # 新建线程
    t = threading.Thread(target=action, name="xdq thread {}".format(i), args=(i, ), daemon=True)
    # 获取线程状态
    daemon_t = t.isDaemon()
    print("Daemon value: {}".format(daemon_t))
    # 开启线程
    t.start()
    # 获取活动线程列表
    enu = threading.enumerate()
    # 获取活动线程数量
    count = threading.active_count()
    print("Threading counts: {}".format(count))
    print("Enumerate thread: {}".format(enu))
# 主线程
print("Main thread end!")
  • Result
Daemon value: True
Threading counts: 6
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , ]
Daemon value: True
Threading counts: 7
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , , ]
Daemon value: True
Threading counts: 8
Enumerate thread: [<_MainThread(MainThread, started 139949210203968)>, , , , , , , ]
Main thread end!
Current thread: xdq thread 0
Threading No.:0
Current thread: xdq thread 1
Threading No.:1
Current thread: xdq thread 2
Threading No.:2

此时线程自动结束:


Python之线程threading_第2张图片

图4.1 daemon=False运行结果

4.4.3 分析

  • 默认程序的线程有5个,新建的线程线程在最后一个;
  • dameon为False时即新建的线程为前台(非后台)线程,主线程结束后,前台进程继续执行,当所有线程运行结束后,不会自动结束线程.若此时线程较多,子线程结束,机会继续执行下一个线程,会造成运行混乱;
  • daemon为True时,即新建的线程为后台线程,主线程结束后,后台线程继续执行,执行完毕后会自动结束所有线程;

4.5 join方法

threading.join()等待线程结束后,在执行下一个线程,保护线程通畅有序执行.

4.5.1 daemon=False不使用使用join

  • Demo1
import threading
from threading import Thread, Lock

def add(a, b):
    result = a + b
    print("result: {} \n".format(result))
    desc_th = threading.enumerate()
    print("Thread describe: {}".format(desc_th))
    count_th = threading.active_count()
    print("Counts of thread: {}".format(count_th))
        
def py_thread():
    for i in range(3):
        th_1 = Thread(target=add, args=(2, 2), name="a"+str(i), daemon=False)
        th_1.start()
   
if __name__ == "__main__":
    py_thread()
    print("starting")
  • Result
result: 4 

result: 4 

Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , , ]
Counts of thread: 7
Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , , , ]result: 4 

Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , , , ]
Counts of thread: 8

Counts of thread: 7
starting

4.5.2 daemon=False使用join

  • Demo2
import threading
from threading import Thread, Lock

def add(a, b):
    result = a + b
    print("result: {} \n".format(result))
    desc_th = threading.enumerate()
    print("Thread describe: {}".format(desc_th))
    count_th = threading.active_count()
    print("Counts of thread: {}".format(count_th))
        
def py_thread():
    for i in range(3):
        th_1 = Thread(target=add, args=(2, 2), name="a"+str(i), daemon=False)
        th_1.start()
        th_1.join()
   
if __name__ == "__main__":
    py_thread()
    print("starting")
  • Result
result: 4 

Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , ]
Counts of thread: 6
result: 4 

Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , ]
Counts of thread: 6
result: 4 

Thread describe: [<_MainThread(MainThread, started 140423660799808)>, , , , , ]
Counts of thread: 6
starting

4.5.3 解析

  • daemon=False模式下,线程运行后不自动结束,因此多线程运行时,会出现混乱,不使用等待线程结束的方法join时,如Demo1所示,有三个线程,第一个线程运行时,还未运行结束,第二个线程开启了,出现先后输出计算结果的情况,即出现了线程混乱;
  • 使用join方法,即等待线程开启后,等待线程中的所有任务执行后,再去开启下一个线程,保证了当前线程的完整生命周期,这也解决了线程混乱的问题,如Demo2所示,有三个线程,第一个线程的任务完全结束后,才会开启第二个线程,因此,输出结果是有序的.

5 Lock&RLock

5.1 小序

Python线程间是随机调度的,无优先级,即一个线程a执行多次后,接着执行b线程,当存在多个线程时,容易造成线程混乱,造成资源浪费.
Lock&RLock应运而生,基元锁是同步基元,当锁定时不被特定线程占用.Python中,Lock是可用的最低级别的同步基元,可直接被_thread扩展模块使用.
Lock基元有锁定和非锁定两种状态,并且他在非锁定状态下创建,拥有acquire()release()两种方法,在非锁定状态时,acquire()改变线程为锁定状态并立即返回.在锁定状态时,acquire()阻塞,直到release()调用另一个线程将其改变为非锁定状态,然后acquire()重置该线程为锁定状态,并立即返回.release()方法只在锁定状态是调用,他将线程状态修改为非锁定状态,并立即返回.
当多个线程在acquire()中处于阻塞状态等待改变为非锁定状态是,release()只处理一个线程,将其重置为非锁定并返回.

5.2 锁(Lock)方法

import threading
l = threading.Lock()
l.acquire()
序号 方法 描述
1 acquire(blocking=True, timeout=-1) 获取锁,阻塞或非阻塞,当阻塞参数值为True(默认)时,为阻塞状态,锁为非锁定状态时变为非阻塞状态.,设定为锁定,立即返回;blocking为False时,不阻塞,当blocking设定为True时,线程锁定,返回False,锁定时返回True
2 release() 释放锁,任意线程均可调用,不局限于有lock的线程

5.3 锁应用

5.3.1 使用锁

  • Demo
import threading
import time

g1 = 0
lock = threading.RLock()
def func():
    lock.acquire()
    global g1
    g1 += 1
    time.sleep(1)
    print("Global value: {}".format(g1))
    lock.release()
    
for i in range(10):
    t = threading.Thread(target=func)
    t.start()
  • Result
Global value: 1
Global value: 2
Global value: 3
Global value: 4
Global value: 5
Global value: 6
Global value: 7
Global value: 8
Global value: 9
Global value: 10

5.3.2 不使用锁

  • Demo
import threading
import time

g1 = 0
lock = threading.RLock()
def func():
    
    global g1
    g1 += 1
    time.sleep(1)
    print("Global value: {}".format(g1))
 
for i in range(10):
    t = threading.Thread(target=func)
    t.start()
  • Result
Global value: 10
Global value: 10Global value: 10

Global value: 10
Global value: 10
Global value: 10
Global value: 10
Global value: 10
Global value: 10Global value: 10

5.3.3 分析

  • 线程使用锁时,输出结果按照预定结构输出,全局变量每次被调用时都要获得锁,才能操作,保证了共享数据的安全性,资源分配合理;
  • 线程不使用锁是,多次运行输出混乱;

5.3.4 阻塞&非阻塞

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起(如IO操作),调用线程只有在得到结果之前才会返回,数据没来,啥也不做,直到数据来了,才进行下一步处理.
  • 非阻塞在不能立刻得到结果之前也会立即返回,同时该函数不会阻塞当前线程,数据没来,进程不断检测数据,包括其他线程或进程数据,直到数据到来.

6 总结

  • 默认主程序有5个线程;
  • daemon的状态不影响新建线程的运行;
  • daemon=False时,主程序结束后,新建的线程不会自动结束,需要手动结束;
  • daemon=True时,主程序,结束后,新建的线程会在运行结束后自动结束,无需手动关闭;
  • 线程使用锁,运行结果可控,当一个线程未得到请求结果时,其他线程会挂起等待,直到当前进程结束,下一线程继续请求获取结果,不使用锁是,线程运行会造成混乱,资源不能合理利用;
  • 阻塞和非阻塞是针对进程或线程而言,阻塞即当请求不能满足时,挂起该进程或线程,非阻塞不会阻塞当前进程或线程;

[参考文献]
[1]https://docs.python.org/3/library/threading.html
[2]https://www.cnblogs.com/tkqasn/p/5700281.html
[3]https://blog.csdn.net/qq_25343557/article/details/78965044
[4]https://github.com/python/cpython/blob/3.7/Lib/threading.py
[5]https://www.cnblogs.com/xfiver/p/5189732.html
[6]https://www.cnblogs.com/xiao-apple36/p/8683198.html


你可能感兴趣的:(#,Python三包)