Python 多线程编程-01-threading 模块初识

目   录

1. 多线程基础知识

1.1 进程和线程的定义与区别?

1.2 Python 的全局解释器锁

1.3 在不同的 OS 上,在 Python 中使用线程

1.4 thread 模块和 threading 模块比较

2. threading 模块使用详解

2.1 threading 模块的对象

2.1.1 Thread :执行线程的对象

 2.1.2 Lock :锁原语对象

2.1.3 RLock:可重入锁对象

2.1.4 Condition:条件变量对象       

2.1.5 Event:条件变量的通用版本

2.1.6 Semaphore:有限资源计数器

2.1.7 BoundedSemaphore:

2.1.8 Timer:定时器

2.1.9 Barrier:障碍

2.2 threading 模块的其他函数

2.2.1 active_count()

2.2.2 current_thread

2.2.3 enumerate()

2.2.4 main_thread()

2.2.5 get_ident()

2.2.6 setrace(func)

2.2.7 setprofile(func)

2.2.8 stack_size(size=0)

3. threading.Thread 调用

3.1 threading.Thread 构造函数

3.1.1 构造方法

3.1.2 参数列表

3.2 threading.Thread 使用示范

3.2.1 创建 Thread 的实例,传递给它一个函数

3.2.2 创建 Thread 的实例,传递给它一个可调用的类实例

3.2.3 派生 Thread 的子类,并创建子类的实例



Python 多线程编程目录

Python 多线程编程-01-threading 模块初识

Python 多线程编程-02-threading 模块-锁的使用

Python 多线程编程-03-threading 模块 - Condition

Python 多线程编程-04-threading 模块 - Event

Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore

Python 多线程编程-06-threading 模块 - Timer

Python 多线程编程-07-threading 模块 - Barrie

1. 多线程基础知识

1.1 进程和线程的定义与区别?

        进程是一个执行中程序,每一个进程都有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上面的所有进程,并为这些进程合理的安排时间。一个进程也可以通过 fork 或者 spawn 新的进程来执行其他任务。进程之间通过 IPC 进行通信。

        进程的相关知识,可以看我的这个专栏:操作系统_一分耕耘一分收获-CSDN博客

        线程和进程类似,但是它是在进程下工作的,一个进程可能拥有多个线程,彼此之间享有共同的上下文或者说数据空间。可以把它看成是迷你进程。线程之间的信息通信和数据共享更加容易,但是因为两个或者多个线程访问同一片数据空间,不同的访问顺序会导致不同的结果,这种情况称之为竞态条件(race condition)。绝大多数线程库都有同步原语来解决这个问题,一般是通过线程管理器来控制执行和访问。

        一个线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。它可以被抢占资源(中断),可以临时挂起(睡眠),这种做饭也叫做让步。在多线程之间,不类似多进程,OS 会尽可能公平地给与时间片,线程如果没有专门进行为多线程的情况进行修改,那么线程在执行一些程序时候会保持阻塞状态,CPU的时间分配会被这些贪婪的程序多用。

1.2 Python 的全局解释器锁

        Python 代码的执行是由 Python 虚拟机(又叫解释器主循环)进行控制的。Python 的设计思路是,在主循环中同时只能有一个控制线程在执行,就像单核 CPU 系统中的多进程一样,只是在宏观上看起来多进程,在微观上其实还得单个单个的运行。同理,尽管 Python 解释器中可以有多个线程在运行,但是微观时间上,只有一个线程在被真正执行。

        而对 Python 虚拟机的访问,就是由全程解释器锁(Global Interpreter Lock)来控制的。这个锁就是用来保证同时只能由一个线程运行的。它的方式:

  1. 设置GIL
  2. 切换一个线程去运行
  3. 执行下面的操作
  4. 把线程设置回睡眠状态(切换出线程)

            a) 指定数量的字节码指令

            b) 线程主动让出控制权(可以用 tims.sleep())实现

  5. 解锁GIL
  6. 重复 1~5

        请注意,从上面的描述,我们能够得到一个关键信息:I/O 密集型的 Python 程序比计算密集型的代码能够更好地利用多线程!如果我们的程序要处理的是计算密集型的情况,请慎用多线程编程,否则频繁的切换线程可能反而会带来性能上的下降。

1.3 在不同的 OS 上,在 Python 中使用线程

        Python 支持多线程编程,但是还取决于它所处的 OS 是否支持。绝大多数操作系统是支持多线程的:绝大多数 Unix 平台(Linux、Solaris、Mac OS X、*BSD)等,以及 Windows 平台。此外,Python 使用兼容 POSIX 的线程,也就是众所周知的 pthread。

1.4 thread 模块和 threading 模块比较

        在 Python 2.X 时代,使用最基础的 thread 模块只需要 import thread 语句就可以了,但是进入了 Python 3.X 时代,thread 模块越来越式微,名字甚至被改为了 _thread 。

        推荐大家使用 threading 模块而不是 thread 模块的原因如下:

  • threading 模块更先进,由更好的线程支持;
  • thread 模块只拥有一个同步原语,而 threading 模块拥有很多
  • thread 模块没有控制线程如何退出,当主线程结束时候,其他的线程也强制结束,不会发出警告或者进行适当的清理。这就造成 thread 模块不支持守护线程这个概念。而这在多线程使用中是非常普遍的。
  • thread 模块中的一些属性和 threading 模块冲突;

2. threading 模块使用详解

2.1 threading 模块的对象

2.1.1 Thread :执行线程的对象

        首先,创建一个 Thread 类对象。

import threading
t1=threading.Thread()

        可以看到它有很多方法和属性。 

threading.Thread 类的属性和方法
序号属性和方法描述
1属性:daemon布尔标识。表示这个线程是否是守护线程。
2属性:ident线程标识符
3属性:name线程名
4属性:native_id线程 ID,该非负数可用于在系统范围内唯一标识此特定线程。
5方法:getName()返回线程名
6方法:is_alive()返回布尔标识。表示这个线程是否是否存活。
7方法:isAlive() 驼峰式命名已经被弃用。#DeprecationWarning: isAlive() is deprecated, use is_alive() instead
8方法:isDaemon()返回布尔标识。表示这个线程是否是守护线程,是则 True,否则是 False。
9方法:start()开始执行该线程,请注意,一个线程只能开始执行依次,反复调用这个方法会报错:threads can only be started once;
10方法:join()直至启动的线程终止之前一直挂起,除非给出了 timeout(秒),否则就会一直阻塞。请注意,得先 start 才能 join。cannot join thread before it is started
11方法:run()定义线程功能的方法,通常在子类中被应用开发者重现
12方法:setDaemon(daemonic)已经弃用,应该直接设置属性 daemon,也可以在实例化中设置
13方法:setName("abc")设置线程名

 
2.1.2 Lock :锁原语对象

        首先,创建一个 Lock 类对象。

lock_example=threading.Lock()

threading.Lock 类的属性和方法
序号属性和方法描述
1

方法:acquire(blocking=True,

timeout=-1)

如果没有参数,且这个锁已经被锁定了,哪怕是被同一个线程锁定,这个线程将会阻塞,需要等待另外一个线程释放获取锁后,返回 True。

如果有参数,只有当参数是 True时候,才会阻塞。

方法的返回值反应释放获取了锁。阻塞操作是可以中断的。

2方法:acquire_lock()已经废弃
3方法:locked()返回一个布尔值,指示锁的状态
4方法:locked_lock()已经废弃
5方法:release()释放锁,允许在等待该锁的阻塞队列中的某个线程获得这个锁。这个锁当前应该是被锁定的状态,但是不能是锁定这个锁的线程再次锁定它。
6方法:release_lock()已经废弃

详细使用请见Python 多线程编程-02-threading 模块-锁的使用

2.1.3 RLock:可重入锁对象

         首先,创建一个 RLock 类对象。RLock 类对象使单一线程可以(再次)获得已持有的锁(锁递归)。

threading.RLock 类的属性和方法
序号属性和方法描述
1

方法:acquire

(blocking=True)

锁定锁,返回一个布尔值指示是否锁定。blocking 指示我们是否等待这个锁可用。blocking = False 且另外一个线程占据了这个锁,那么立刻返回 False。blocking = True, 且另外一个线程占据了这个锁,那么则等待这个锁释放,拿到这个锁,然后返回 True。

请注意,阻塞操作是可中断的。在所有其他情况下,该方法将立即返回True。

确切地说,如果当前线程已经持有锁,则其内部计数器简单地递增。如果没有人拿着锁,获取锁,其内部计数器初始化为1。

2方法:release()释放锁,允许在等待该锁的阻塞队列中的某个线程获得这个锁。这个锁当前应该是被锁定的状态,但是必须是锁定这个锁的线程再次锁定它。

   详细使用请见Python 多线程编程-02-threading 模块-锁的使用      

2.1.4 Condition:条件变量对象       

        高级用法,详细见:Python 多线程编程-03-threading 模块 - Condition

2.1.5 Event:条件变量的通用版本

        高级用法,详细见:Python 多线程编程-04-threading 模块 - Event


2.1.6 Semaphore:有限资源计数器

        为线程间共享的有限资源提供了一个计数器,如果没有资源时候会被阻塞。

        高级用法,详细见:Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore

2.1.7 BoundedSemaphore:

        和 Semaphore 类似,但是不允许超过初始值。

        高级用法,详细见:Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore


2.1.8 Timer:定时器

        高级用法,详细见:Python 多线程编程-06-threading 模块 - Timer


2.1.9 Barrier:障碍

        创造一个障碍,必须达到指定数量的线程后才可以继续。

        高级用法,详细见:Python 多线程编程-07-threading 模块 - Barrier

2.2 threading 模块的其他函数

2.2.1 active_count()

        返回当前活动的Thread 对象个数。返回值和通过enumerate()返回的列表长度是相等的。

2.2.2 current_thread

        返回当前活动的Thread 对象,对应调用者的控制线程。如果调用者的控制线程不是通过threading 模块创建,一个功能受限的虚拟线程被返回。

2.2.3 enumerate()

        返回当前活动的Thread 对象列表。该列表包括精灵线程、被current_thread()创建的虚拟线程对象、和主线程。它不包括终止的线程和还没有启动的线程。

2.2.4 main_thread()

        返回主线程对象。在正常情况下,主线程就是Python解释器启动的线程。

        ​ 

2.2.5 get_ident()

        返回当前线程的“线程标识符”。这是一个非0整数,没有特定含义,通常用于索引线程特定数据的字典。线程标识符可以被循环使用。

2.2.6 setrace(func)

        为所有线程设置一个 trace 函数。为所有从 threading 模块启动的线程设置一个 trace 函数。在每个线程的 run() 方法被调用前,函数将为每个线程被传递到 sys.settrace()。

2.2.7 setprofile(func)

        为所有线程设置一个 profile 函数。

2.2.8 stack_size(size=0)

        返回新创建线程的栈大小;或者为后续创建的线程设定栈的大小为 size。

3. threading.Thread 调用

3.1 threading.Thread 构造函数

3.1.1 构造方法

t=threading.Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)

3.1.2 参数列表

  1. group:默认为 None,是为后续实现线程组时候使用的。
  2. target:默认为 None,不为None 时候是一个可调用的对象,指定了启动线程时候进行的操作。
  3. name:默认为 None,不为None 时候指定线程的名字,否则由系统指定以 "Thread-N" 开头的线程名。
  4. args:和 target 连用,作为 target 的参数列表
  5. kwargs:和 target 连用,作为 target 的参数字典
  6. daemon:默认为 None,是否是守护线程

3.2 threading.Thread 使用示范

        使用 threading.Thread 类,可以有很多方法创建线程。主要有三种:

  • 创建 Thread 的实例,传递给它一个函数
  • 创建 Thread 的实例,传递给它一个可调用的类实例
  • 派生 Thread 的子类,并创建子类的实例

3.2.1 创建 Thread 的实例,传递给它一个函数

        请注意此处在创建 Thread 类时候就指定了其调用的目标函数和参数。

import threading
import time
def say_time(nSeconds,name):
    print("{0} Starting In saytime:{1}".format(name,time.ctime()))
    print("   I will sleep for {0} seconds!".format(nSeconds))
    time.sleep(nSeconds)
    print("{0} Ending In saytime:{1}".format(name,time.ctime()))

n_loops=list(range(5))

def main():
    threads=[]
    # 创建线程,并且指定函数
    for i in n_loops:
        thread=threading.Thread(target=say_time,args=(i+1,i))
        threads.append(thread)
    # 依次启动线程
    for i in n_loops:
        threads[i].start()
    
    for i in n_loops:
        threads[i].join()
    
    print("All done! {0}".format(time.ctime()))
    
main()

​  

        .join() 方法等待线程结束,或者在提供了超时时间的情况,达到超时时间。使用 .join() 方法要比等待锁释放的无限循环更加清晰。此处的 .join() 可有可无,因为主程序之后没有必须等待 thread 完成的工作,但是如果不使用 .join(),对于某些对顺序有严格要求的程序,可能不合适。 如果我把此处的  .join() 注释掉,程序各条语句完成的顺序就不一样了。

import threading
import time
def say_time(nSeconds,name):
    print("{0} Starting In saytime:{1}".format(name,time.ctime()))
    print("   I will sleep for {0} seconds!".format(nSeconds))
    time.sleep(nSeconds)
    print("{0} Ending In saytime:{1}".format(name,time.ctime()))

n_loops=list(range(5))

def main():
    threads=[]
    
    for i in n_loops:
        thread=threading.Thread(target=say_time,args=(i+1,i))
        threads.append(thread)
    
    for i in n_loops:
        threads[i].start()
    
    #for i in n_loops:
        #threads[i].join()
    
    print("All done! {0}".format(time.ctime()))
    
main()

        

3.2.2 创建 Thread 的实例,传递给它一个可调用的类实例

        这种写法很累赘,目前我还没有看出来有什么好处。不过,这是一种实现方式。

import threading
import time
class Say_Time(object):
    def __init__(self,func,args,name=" "):
        self.name=name
        self.func=func
        self.args=args
    
    def __call__(self):
        self.func(*self.args)
        
def say_time(nSeconds,name):
    print("{0} Starting In saytime:{1}".format(name,time.ctime()))
    print("   I will sleep for {0} seconds!".format(nSeconds))
    time.sleep(nSeconds)
    print("{0} Ending In saytime:{1}".format(name,time.ctime()))

n_loops=list(range(3))

def main():
    threads=[]
    print("All Starting! {0}".format(time.ctime()))
    
    
    for i in n_loops:
        print(say_time,18+i,"name"+str(i))
        thread=threading.Thread(target=Say_Time(say_time,(18+i,"name"+str(i))))
        threads.append(thread)
    
    for i in n_loops:
        threads[i].start()
    
    for i in n_loops:
        threads[i].join()
    
    print("All done! {0}".format(time.ctime()))
    
main()

3.2.3 派生 Thread 的子类,并创建子类的实例

这种方法和第一种方法用得较多。

import threading
import time
class MyThread(threading.Thread):
    def __init__(self,func,args,name=" "):
        threading.Thread.__init__(self)
        self.name=name
        self.func=func
        self.args=args
    
    # run() 定义线程功能的方法,通常在子类中被应用开发者重现
    def run(self):
        self.func(*self.args)
        
def say_time(nSeconds,name):
    print("{0} Starting In saytime:{1}".format(name,time.ctime()))
    print("   I will sleep for {0} seconds!".format(nSeconds))
    time.sleep(nSeconds)
    print("{0} Ending In saytime:{1}".format(name,time.ctime()))

n_loops=list(range(3))

def main():
    threads=[]
    print("All Starting! {0}".format(time.ctime()))
    
    
    for i in n_loops:
        print(say_time,18+i,"name"+str(i))
        thread=MyThread(say_time,(18+i,"name"+str(i)),say_time.__name__)
        threads.append(thread)
    
    for i in n_loops:
        threads[i].start()
    
    for i in n_loops:
        threads[i].join()
    
    print("All done! {0}".format(time.ctime()))
    
main()

'''

要是大家觉得写得还行,麻烦点个赞或者收藏吧,想个博客涨涨人气,非常感谢!

'''

  • 25
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南野栀子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值