python --- 基础多线程编程

简介: 在python中进行多线程编程之前必须了解的问题:1. 什么是线程?   答:线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

在python中进行多线程编程之前必须了解的问题:


1. 什么是线程?
  答:线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

2. 什么是多线程?
  答:在单个程序中同时运行多个线程完成不同的工作,称为多线程。

3. 多线程编程的目的?
  答:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

4. 如何再python中执行多线程编程?
  答:在python2.x的版本中提供了thread(这个模块为多线程提供了一个底层 、原始的操作[也可以成为light-weight processes 或者 tasks) — 多个控制线程共享全局数据空间。为了多线程同步,提供了简单的锁(也称呼为 mutexes 后者 binary semaphores) 。]和threading(本模块的高层线程接口构建在低层的thread模块上)两个模块用于线程操作;而在python3.x中,官方只给出了threading模块的文档,对于底层线程造作放在了_thread模块中(即不建议使用)。是故在python中使用threading模块编程即可。

 

例一(一个简单的双线程程序):

 1 import threading
 2 import time
 3 
 4 def run(n):
 5     print("task-%s" % n)
 6     time.sleep(5)
 7 
 8 #实例化一个线程对象,target传入任务名,args以元组的形式传入任务函数的参数
 9 task1 = threading.Thread(target=run, args=(1,))
10 task2 = threading.Thread(target=run, args=(2,))
11 
12 task1.start()   #线程启动
13 task2.start()
test_threads_1

  注:执行上面这个程序一共花费5秒左右的时间,而如果分别调用两次run怎需要10秒(证明两个任务同时运行)。

 

例二(用面向对象编程实现例二,并讲解threading模块中的一些常用方法):

 1 import threading
 2 import time
 3 
 4 class Task(threading.Thread):
 5     def __init__(self, n):
 6         super(Task, self).__init__() #重构了__init__(), 所以先执行父类的构造函数
 7         self.n = n     #构造函数中传入任务参数参数
 8 
 9     #任务函数
10     def run(self):
11         """
12         不同于例一,在类中,任务函数只能以run命名,参数从构造函数中传入
13         :return: 
14         """
15         print("task-%s" % self.n)
16         # print(threading.current_thread())   #threading.current_thread()返回当前的Thread对象,对应于调用者控制的线程。
17         time.sleep(2)
18 
19 #实例化并启动
20 t1 = Task(1)
21 t2 = Task(2)
22 # t1.setDaemon(True)     #将t1设置成为守护线程
23 # t2.setDaemon(True)     
24 
25 t1.start()
26 #t1.join(timeout=5)  #等待t1结束再运行t2,timeout代表等待时间,超过5秒便不再等待。
27 t2.start()
28 
29 # print(threading.current_thread())
30 # print(threading.active_count())  #threading.active_count()返回当前处于alive状态的Thread对象的个数。
test_threads_2

  :建议使用例二这种编程方式,在类中,任务函数只能以run命名,参数从构造函数中传入。

  注:有时候难免会遇到一个问题,主程序的执行需要某个子线程(即使用start启动的线程,默认子线程一旦启动主程序将继续运行,两者不再有关联)的执行结果,这时使用join即可,例如例二中的t1.join()。

  守护线程:一种特殊的子线程,与主线程(即本文中的主程序,由程序使用者启动)存在一种“主死仆死”的关系,即主线程意外停止或运行结束,不管守护线程是否运行完毕都得终止(非守护线程的子线程启动后与主线程没有联系,不论主线程是否还在运行,子线程不受影响),一个线程是否为守护线程继承于创建它的线程(将一个子线程设置成为守护线程可参考例二,去掉22,23行注释,则主程序结束t1,t2也结束,不会再sleep)。

 

  在进行多线程编程时,经常会有多个线程同时对同一份数据修改,这便会导致最终得得到的数据不是预期结果。所以需要在一个线程修改数据时将数据锁定,只允许当前线程修改,当修改完成后,解锁让其他线程修改。在threading模块中使用Lock Objects解决此问题。

Lock Objects:   

  A primitive lock is a synchronization primitive that is not owned by a particular thread when locked. In Python, it is currently the lowest level synchronization primitive available, implemented directly by the _thread extension module.

A primitive lock is in one of two states, “locked” or “unlocked”. It is created in the unlocked state. It has two basic methods, acquire() and release(). When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in another thread changes it to unlocked, then the acquire() call resets it to locked and returns. The release() method should only be called in the locked state; it changes the state to unlocked and returns immediately. If an attempt is made to release an unlocked lock, a RuntimeError will be raised.

  Locks also support the context management protocol.

  When more than one thread is blocked in acquire() waiting for the state to turn to unlocked, only one thread proceeds when a release() call resets the state to unlocked; which one of the waiting threads proceeds is not defined, and may vary across implementations.

  All methods are executed atomically.

  例三(多个线程对一个全局变量进行加1操作):

 1 import threading,time
 2 
 3 
 4 n = 5
 5 
 6 class Task(threading.Thread):
 7     def __init__(self, n):
 8         super(Task, self).__init__()
 9         self.name = "Thread-" + str(n)
10 
11     def run(self):
12         """
13         不同于例一,在类中,任务函数只能以run命名,参数从构造函数中传入
14         :return: 
15         """
16         global n
17         time.sleep(0.5)
18         #锁定资源, 此时关于n的操作只能此线程执行
19         if lock.acquire():
20             n += 1
21             print("%s, n = %s" % (self.name, n))
22             time.sleep(0.2)
23             lock.release()   #解锁
24 
25 if __name__ == "__main__":
26 
27     lock = threading.Lock()         #实例化一个锁,此时锁处于打开状态
28 
29     ts = []    #用于存储线程对象
30     #循环启动50个线程
31     for i in range(1, 50+1):
32         t = Task(i)
33         ts.append(t)
34         t.start()
35     #等待所有线程结束
36     start_time = time.time()
37     for t in ts:
38         t.join()
39 
40     print("run time:",time.time() - start_time)
test_threads_3

   注:运行此程序,可发现一共用时10.5(0.2*50 + 0.5)秒左右,可以发现在锁定代码段程序没有并行执行。

  注:有时候需要多层加锁,这时Lock Objects已经满足不了这个需求(当Lock处于locked时,遇到下一个acquire()时会阻塞)。这时我们使用RLock Objects,它的调用同Lock Objects。

 

信号量:信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。

  举例说明:以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

  在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
  信号量的特性:信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。
    简单来说,当使用Lock或RLock的locked期间,没有并发执行(只unlocked后才并行);而使用Semaphore可以让有限个线程共同访问资源,它的使用也是只有acquire()和release()。
  例四(信号量的使用):
 1 import threading
 2 import time
 3 
 4 class Task(threading.Thread):
 5     #任务函数
 6     def __init__(self, n):
 7         super(Task, self).__init__()
 8         self.name = "Thread-" + str(n)
 9 
10     def run(self):
11         semaphore.acquire()
12         print("%s" % self.name)
13         time.sleep(2)
14         semaphore.release()
15 
16 
17 if __name__ == "__main__":
18 
19     semaphore = threading.BoundedSemaphore(5)   #只允许5个线程同时访问资源
20 
21     ts = []    #用于存储线程对象
22     #循环启动50个线程
23 
24     for i in range(1, 23+1):
25         t = Task(i)
26         ts.append(t)
27         t.start()
test_threads_4

 

   注:通过运行上面的代码,可以发现在关键代码段最多只有5个线程在同时运行。
    每调用一次acquire(),Semaphore的计数器减1;每调用一次release(),Semaphore的计数器加1,最大不超过设置的计数器初始值。
    
   既然有了多线程编程,那么自然也就有了线程之间的交互。在python中threading模块提供了Event Objocts实现这个功能。
Events Objects:
   This is one of the simplest mechanisms for communication between threads: one thread signals an event and other threads wait for it(事件对象是线程间最简单的通信机制之一:线程可以激活在一个事件对象上等待的其他线程).

  An event object manages an internal flag that can be set to true with the set() method and reset to false with the clear() method. The wait() method blocks until the flag is true(每个事件对象管理一个内部标志,可以在事件对象上调用set() 方法将内部标志设为true,调用 clear() 方法将内部标志重置为false。wait()方法将阻塞直至该标志为真。).

  例五(简单的线程交互程序,演示Event Objects的使用):

 1 import threading,time
 2 
 3 class Task1(threading.Thread):
 4     def run(self):
 5         while True:
 6             for i in range(1, 20+1):
 7                 print("i = ", i)
 8                 if i % 5 == 0:
 9                     eve.set()    #到5的倍数,将Event内部标志设置为True
10                 time.sleep(0.5)
11 
12 class Task2(threading.Thread):
13     def run(self):
14         while True:
15             #检测内部标志是否为True
16             if eve.is_set():
17                 print("hello world")
18                 time.sleep(2)
19                 eve.clear()       #重置Event内部标志
20             else:
21                 eve.wait()         #内部标志不为True,此线程处于阻塞状态
22 
23 if __name__ == "__main__":
24     t1 = Task1()
25     t2 = Task2()
26     eve = threading.Event()      #实例化一个Event Objects
27 
28     t1.start()
29     t2.start()
test_threads_5
目录
相关文章
|
2月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
178 6
|
3月前
|
数据采集 机器学习/深度学习 人工智能
Python:现代编程的首选语言
Python:现代编程的首选语言
318 102
|
3月前
|
数据采集 机器学习/深度学习 算法框架/工具
Python:现代编程的瑞士军刀
Python:现代编程的瑞士军刀
345 104
|
3月前
|
人工智能 自然语言处理 算法框架/工具
Python:现代编程的首选语言
Python:现代编程的首选语言
274 103
|
3月前
|
机器学习/深度学习 人工智能 数据挖掘
Python:现代编程的首选语言
Python:现代编程的首选语言
206 82
|
2月前
|
Python
Python编程:运算符详解
本文全面详解Python各类运算符,涵盖算术、比较、逻辑、赋值、位、身份、成员运算符及优先级规则,结合实例代码与运行结果,助你深入掌握Python运算符的使用方法与应用场景。
211 3
|
2月前
|
数据处理 Python
Python编程:类型转换与输入输出
本教程介绍Python中输入输出与类型转换的基础知识,涵盖input()和print()的使用,int()、float()等类型转换方法,并通过综合示例演示数据处理、错误处理及格式化输出,助你掌握核心编程技能。
482 3
|
2月前
|
并行计算 安全 计算机视觉
Python多进程编程:用multiprocessing突破GIL限制
Python中GIL限制多线程性能,尤其在CPU密集型任务中。`multiprocessing`模块通过创建独立进程,绕过GIL,实现真正的并行计算。它支持进程池、队列、管道、共享内存和同步机制,适用于科学计算、图像处理等场景。相比多线程,多进程更适合利用多核优势,虽有较高内存开销,但能显著提升性能。合理使用进程池与通信机制,可最大化效率。
297 3
|
2月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
304 0
|
10月前
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
512 0

推荐镜像

更多