使用多线程注意点避免产生脏数据。如下三种方式避免产生脏数据。
第一种互斥锁
l = threading.Lock()
l.lock()
a = 10
l.release()
第二种使用本地变量
def thread_cal1():
a = 10
for _ in xrange(10000):
t =threading.Thread(target=thread_cal1)
t.start()
t.join()
第三种使用threadlocal
global_data = threading.local()
def thread_cal2():
t = threading.Thread(target=thread_cal2)
t.start()
全局 VS 局部变量
<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
首先借助一个小程序来看看多线程环境下全局变量的同步问题。
import threading
global_num = 0
def thread_cal():
global global_num
for i in xrange(1000):
global_num += 1
# Get 10 threads, run them and wait them all finished.
threads = []
for i in range(10):
threads.append(threading.Thread(target=thread_cal))
threads[i].start()
for i in range(10):
threads[i].join()
# Value of global variable can be confused.
print global_num
这里我们创建了10个线程,每个线程均对全局变量 global_num 进行1000次的加1操作(循环1000次加1是为了延长单个线程执行时间,使线程执行时被中断切换),当10个线程执行完毕时,全局变量的值是多少呢?答案是不确定。简单来说是因为 global_num += 1 并不是一个原子操作,因此执行过程可能被其他线程中断,导致其他线程读到一个脏值。以两个线程执行 +1 为例,其中一个可能的执行序列如下(此情况下最后结果为1):
多线程中使用全局变量时普遍存在这个问题,解决办法也很简单,可以使用互斥锁、条件变量或者是读写锁。下面考虑用互斥锁来解决上面代码的问题,只需要在进行 +1 运算前加锁,运算完毕释放锁即可,这样就可以保证运算的原子性。
l = threading.Lock()
...
l.acquire()
global_num += 1
l.release()
在线程中使用局部变量则不存在这个问题,因为每个线程的局部变量不能被其他线程访问。下面我们用10个线程分别对各自的局部变量进行1000次加1操作,每个线程结束时打印一共执行的操作次数(每个线程均为1000):
def show(num):
print threading.current_thread().getName(), num
def thread_cal():
local_num = 0
for _ in xrange(1000):
local_num += 1
show(local_num)
threads = []
for i in range(10):
threads.append(threading.Thread(target=thread_cal))
threads[i].start()
可以看出这里每个线程都有自己的 local_num,各个线程之间互不干涉。
Thread-local 对象
上面程序中我们需要给 show 函数传递 local_num 局部变量,并没有什么不妥。不过考虑在实际生产环境中,我们可能会调用很多函数,每个函数都需要很多局部变量,这时候用传递参数的方法会很不友好。
为了解决这个问题,一个直观的的方法就是建立一个全局字典,保存进程 ID 到该进程局部变量的映射关系,运行中的线程可以根据自己的 ID 来获取本身拥有的数据。这样,就可以避免在函数调用中传递参数,如下示例:
global_data = {}
def show():
cur_thread = threading.current_thread()
print cur_thread.getName(), global_data[cur_thread]
def thread_cal():
global global_data
cur_thread = threading.current_thread()
global_data[cur_thread] = 0
for _ in xrange(1000):
global_data[cur_thread] += 1
show() # Need no local variable. Looks good.
...
保存一个全局字典,然后将线程标识符作为key,相应线程的局部数据作为 value,这种做法并不完美。首先,每个函数在需要线程局部数据时,都需要先取得自己的线程ID,略显繁琐。更糟糕的是,这里并没有真正做到线程之间数据的隔离,因为每个线程都可以读取到全局的字典,每个线程都可以对字典内容进行更改。
为了更好解决这个问题,python 线程库实现了 ThreadLocal 变量(很多语言都有类似的实现,比如Java)。ThreadLocal 真正做到了线程之间的数据隔离,并且使用时不需要手动获取自己的线程 ID,如下示例:
global_data = threading.local()
def show():
print threading.current_thread().getName(), global_data.num
def thread_cal():
global_data.num = 0
for _ in xrange(1000):
global_data.num += 1
show()
threads = []
...
print "Main thread: ", global_data.__dict__ # {}
上面示例中每个线程都可以通过 global_data.num 获得自己独有的数据,并且每个线程读取到的 global_data 都不同,真正做到线程之间的隔离。
ThreadLocal 实现的代码量不多,但是比较难理解,涉及很多 Python 黑魔法,下篇再来分析。那么 ThreadLocal 很完美了?不!Python 的 WSGI 工具库 werkzeug 中有一个更好的 ThreadLocal 实现(https://github.com/pallets/werkzeug/blob/8a84b62b3dd89fe7d720d7948954e20ada690c40/werkzeug/local.py),甚至支持协程之间的私有数据,实现更加复杂,有机会再分析。
相关推荐
Python多线程编程在实现网络串口透传中扮演着重要的角色,特别是在处理TCP客户端网口数据时。网络串口透传技术允许通过网络连接模拟串行通信,使得远程设备可以像连接本地串口一样进行通信。这在物联网(IoT)、远程...
在Python编程中,多线程同步对于文件读写控制至关重要,因为如果不加以控制,多个线程同时访问同一文件可能会导致数据不一致或者错误。这里我们将深入探讨如何在Python中使用多线程同步来确保文件读写的安全性。 ...
总体来看,基于Linux平台的Python多线程爬虫程序设计,不仅可以实现高效的数据抓取,还能通过多种存储方式灵活地管理数据,这对于微博数据挖掘工作来说,是一项十分有价值的工具。而且,随着社交网络数据量的不断...
本离线安装包“python多线程池离线安装包.zip”包含了实现Python多线程所需的关键组件。主要包含以下三个子文件: 1. `pip-19.2.3.tar.gz`:这是Python的包管理器pip的一个版本,用于安装和管理Python库。在离线...
Python多线程编程是利用Python实现程序并行性的一种方式,尤其适合于处理异步、并发事务和资源密集型任务。在多线程环境中,多个线程可以同时执行,提高程序效率,尤其对于那些需要从多个输入源处理数据或者进行大量...
Python多线程编程是Python系统编程中的重要组成部分,它允许程序同时执行多个任务,从而提高程序的效率和响应速度。在Python中,我们通常使用内置的`thread`库、`threading`库以及`Queue`库来实现多线程。 首先,`...
本项目中的"python多线程批量访问url脚本"正是基于这个理念设计的。 该脚本采用了第三方库`grequests`,它是`requests`库的一个并行版本,能够支持异步的HTTP请求。`requests`库是Python中广泛使用的HTTP客户端,而...
### Python多线程学习 在Python中,使用线程主要有两种方法:一是通过函数的方式,二是利用类来封装线程对象。这两种方式都是基于Python的标准库`thread`和`threading`来实现的。 #### 函数方式创建线程 使用`...
Python中的多线程是并发执行任务的一种方式,它允许程序同时处理多个任务,提升程序的效率。在Python中,我们通常使用`threading`模块来实现多线程,而不是`thread`模块,因为`threading`模块提供了更高级别的功能和...
服务端通过多线程接收连接请求,利用非阻塞Socket避免等待数据的阻塞,同时子进程处理接收到的数据。客户端则可以并行地向多个服务端发送数据,进行测试或通信。通过这种方式,我们可以构建出高效、可扩展的网络应用...
本主题“基于Linux的Python多线程爬虫程序设计”将深入探讨如何在Linux环境下利用Python实现多线程爬虫,以优化网络爬虫的性能。 首先,我们需要了解Python中的多线程。Python的`threading`模块提供了创建和管理...
总之,Python的多线程编程是一个复杂但强大的主题,它提供了灵活的多线程支持,但也需要程序员深入了解线程的基本概念、线程库的使用方法以及线程安全的保障措施。通过合理地设计和实现多线程程序,可以显著提高程序...
通过分析和学习这个文件,你可以进一步加深对Python多线程编程的理解,包括如何有效地使用线程池(`ThreadPoolExecutor`),以及如何处理线程异常和线程间的协作问题。 总之,Python的多线程编程虽然受到GIL的限制...
在学习如何使用Python实现多线程抓取数据并存入数据库之前,我们...它涉及对Python多线程编程的理解、数据库操作的知识以及网络数据抓取的实践。只有充分掌握了这些基础知识和技能,才能够高效且稳定地完成这项任务。
Python多线程是指在Python编程语言中使用线程来执行多任务的技术。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。多线程编程是并发编程的一部分,其目的是为了提高程序的...
"Python-python多线程函数库vthread简而强大"就是为了解决这一问题而诞生的。 vthread库是针对Python的一个增强型多线程库,它的主要目标是简化多线程和线程池的使用,提高开发效率。与Python标准库中的`threading`...
在Python编程语言中,多线程是实现并发执行任务的重要机制。它允许程序同时执行多个不同的任务,从而提高效率和响应速度。在这个基于Python的多线程例子中,我们将深入探讨如何创建和管理线程,以及如何利用它们来...
Python多线程和多进程是并行计算的两种主要方法,它们可以显著提高数据获取效率。接下来将详细分析如何利用Python实现多线程获取所有上市公司实时分笔数据。 首先,获取实时数据的挑战: 在传统单线程模式下,我们...
在Python编程中,多线程是实现并发处理的一种方式,特别是在处理大量数据或执行耗时操作时。然而,多线程环境下常常会出现数据共享的问题,这可能导致数据的不一致性和错误。为了解决这个问题,Python提供了多种同步...