Python中关于Thread的一点小知识

最近在实现了一个对sqlite3进行简单封装的异步库aiosqlite,让其支持异步的方式调用。因为是python2.7,标准库中没有原生的类似asyncio的模块,所以依赖第三方tornado库。

由于sqlite3本身查询数据文件的操作是阻塞的,要想实现异步调用,就不得不通过多线程的方式,在执行查询语句的时候通过多线程操作,从而达到伪异步。

使用多线程的过程中,刚好跟同事聊了几句关于多线程的问题,尽管可能是一些基础的知识,但是平时由于没怎么考虑过这块的问题,所以觉得记录一下也好。

以下代码皆基于python2.7版本

Python中当开启的线程任务结束后,线程是否被销毁了?

关于这个问题我理所当然的认为是销毁了,但是同事的看法是:线程执行完成任务后退出,但实际并没有销毁,python本身也没有销毁线程的功能,想销毁线程的话要通过操作系统本身提供的接口。

通常我们在使用Thread时,都是start()开始线程任务,最终join()结束线程,所以看了下cpythonthreading.Thread的源码,关于join()方法的说明中,也并未明确指出线程销毁的问题。

最终还是得通过实践出真知。在CentOS 7 x64系统中写了点测试代码简单验证一下。关于进程的线程数可以通过cat /proc/<pid>/status|grep Thread查看。

import time
from threading import Thread

def t1():
    print('Thread 1 start')
    time.sleep(10)
    print('Thread 1 done')

def t2():
    print('Thread 2 start')
    time.sleep(30)
    print('Thread 2 done')

t1 = Thread(target=t1, daemon=True)
t2 = Thread(target=t2, daemon=True)

for task in (t1, t2):
    task.start()

for task in (t1, t2):
    task.join()
复制代码

开始执行后查看到的Thread数是3,当Thread 1结束后再次查看发现Thread 2数变为2。可见,线程任务结束后,线程销毁了的。

线程任务中,如果其中一个线程阻塞,其他的线程是否还正常运行?

关于这个问题,就显得我有些愚蠢了。由于满脑子想的都是GIL,同一时间只可能有一个线程在跑,那如果这个线程阻塞了,其他的线程肯定也跑不下去了,所以就认为一个线程阻塞,其他的线程肯定也阻塞了。同事的看法则是,肯定不会阻塞,不然还叫什么多线程。

实际通过demo测试后发现,一个线程阻塞并不会对其他线程造成影响。由于对GIL一知半解,所以造成这种错误认知。看了下GIL的资料后了解到,Python的多线程是调用系统多线程接口,GIL只是一把全局锁,一个线程执行时获取到GIL后,执行过程中如果遇到IO阻塞,会释放掉GIL,这样轮到其他的线程执行。所以,不会存在一个线程阻塞,其他线程也跟着阻塞的问题。

这真是个低级的错误。。。

执行多线程任务时,如果其中一个线程中执行了sys.exit()整个进程是否会退出?

同事的看法是会退出,我和另一个同事则不太敢肯定。demo跑跑。

import sys
import time
from threading import Thread

def t1():
    print('Thread 1 start')
    sys.exit()
    print('Thread 1 done')

def t2():
    k = 10
    if k:
        print('Thread 2 is running')
        time.sleep(3)
        k -= 1

t1 = Thread(target=t1, daemon=True)
t2 = Thread(target=t2, daemon=True)

for task in (t1, t2):
    task.start()

for task in (t1, t2):
    task.join()
复制代码

结果是,直到t2运行结束后进程才会退出,t1中的sys.exit()并不会造成整个进程的退出。

看源码sysmodule.c

static PyObject *
sys_exit(PyObject *self, PyObject *args)
{
    PyObject *exit_code = 0;
    if (!PyArg_UnpackTuple(args, "exit", 0, 1, &exit_code))
        return NULL;
    /* Raise SystemExit so callers may catch it or clean up. */
    PyErr_SetObject(PyExc_SystemExit, exit_code);
    return NULL;
}
复制代码

可以看到,返回值总是NULL,但在exit_code不为0时,会set一个PyExc_SystemExit。全局搜索一下PyExc_SystemExit_threadmodule.c中可以找到

...
PyDoc_STRVAR(start_new_doc,
"start_new_thread(function, args[, kwargs])\n\
(start_new() is an obsolete synonym)\n\
\n\
Start a new thread and return its identifier.  The thread will call the\n\
function with positional arguments from the tuple args and keyword arguments\n\
taken from the optional dictionary kwargs.  The thread exits when the\n\
function returns; the return value is ignored.  The thread will also exit\n\
when the function raises an unhandled exception; a stack trace will be\n\
printed unless the exception is SystemExit.\n");

static PyObject *
thread_PyThread_exit_thread(PyObject *self)
{
    PyErr_SetNone(PyExc_SystemExit);
    return NULL;
}
复制代码

其实线程任务正常退出也会set一个PyExc_SystemExit,所以在线程中sys.exit()并不会让整个进程退出。

以上仅为个人见解,如有认知错误,欢迎指正,谢谢。

参考:

Python的GIL是什么

python GIL

Python/sysmodule.c

Modules/_threadmodule.c

<think>好的,我现在需要帮助用户了解Python中的线程使用与实现。用户已经提供了一些引用资料,我需要仔细分析这些资料,并结合自己的知识来组织一个清晰、详细的回答。 首先,引用[1]提到了线程的创建和结束,引用[2]讨论了线程退出的情况以及IO密集型和计算密集型的区别,引用[3]详细说明了线程通讯的各种方法,引用[4]给出了一个创建线程的代码示例。我需要整合这些信息,确保覆盖线程的创建、启动、管理、同步、通信以及应用场景。 接下来,我应该从线程的基本概念开始,解释什么是线程以及它在Python中的应用。然后分步骤介绍如何创建线程,使用threading模块,包括定义函数作为线程目标,传递参数,启动线程等。需要包括代码示例,如引用[4]中的例子,但可能需要简化或调整以确保清晰。 然后,讨论线程的管理,比如等待线程结束(join方法)、线程的生命周期、如何优雅地终止线程。引用[2]提到线程退出可以通过自然结束或调用sys.exit,但需要注意Python中直接终止线程的方法有限,通常建议让线程自然结束。 接下来是线程同步部分,需要涵盖锁(Lock)、条件变量(Condition)、信号量(Semaphore)、事件(Event)等机制,如引用[3]所述。每个机制需要简要说明其用途,并给出简单的代码示例,比如使用Lock来保护共享资源。 线程通信方面,引用[3]提到队列(Queue)是推荐的方式,因为它是线程安全的。需要解释队列的使用方法,并给出生产者-消费者模型的例子,这可能帮助用户理解如何在线程间传递数据。 此外,需要提到GIL(全局解释器锁)的影响,特别是在处理计算密集型任务时,多线程可能无法充分利用多核CPU,而IO密集型任务则能更好地利用多线程。这一点在引用[2]中有提到,应该强调GIL的存在导致Python多线程的适用场景。 最后,应用场景部分需要区分IO密集型和计算密集型,建议在IO操作多时使用多线程,而在计算密集型任务中使用多进程或其他方法。同时,可以提到线程池的使用,如ThreadPoolExecutor,来管理线程资源。 在生成相关问题部分,需要围绕线程的实现细节、同步机制、GIL的影响、应用场景和调试方法展开,确保问题覆盖用户可能感兴趣的后续内容。 需要检查是否符合用户的要求:使用中文,LaTeX公式正确(虽然这个问题可能不涉及太多数学公式,但如果有的话需要确保格式正确),结构清晰,分步骤说明,引用正确标记。同时,确保代码示例正确,并解释关键点。 可能需要注意的地方是,Python中线程的终止方法有限,不能直接杀死线程,这点需要明确说明,避免用户误解。另外,GIL的影响需要详细解释,因为它关系到多线程在Python中的实际效果。 总结回答的结构:概述、创建与启动、线程管理、同步机制、通信方法、GIL的影响、应用场景、注意事项,最后相关问题。确保每个部分有清晰的标题和内容,代码示例适当,引用标注正确。</think>### Python线程使用与实现详解 #### 一、线程概述 Python中的线程通过`threading`模块实现,允许程序并发执行多个任务。线程适用于IO密集型操作(如文件读写、网络请求)[^2],而计算密集型任务受限于全局解释器锁(GIL),需考虑多进程方案[^2]。 #### 二、线程的创建与启动 1. **定义线程函数** 将目标逻辑封装为函数,作为线程执行入口: ```python import threading def task(name): print(f"线程 {name} 启动") ``` 2. **创建线程对象** 使用`Thread`类初始化,通过`target`指定函数,`args`传递参数: ```python thread = threading.Thread(target=task, args=("A",)) ``` 3. **启动线程** 调用`start()`方法启动线程,引用[4]示例展示了多线程并发效果: ```python thread.start() ``` #### 三、线程管理 1. **等待线程结束** 使用`join()`阻塞主线程直至子线程完成: ```python thread.join() ``` 2. **线程生命周期** 线程通过`start()`启动,运行结束后自动退出[^2]。**强制终止**线程在Python中不推荐,应通过标志变量控制: ```python class StoppableThread(threading.Thread): def __init__(self): super().__init__() self._stop_flag = False def stop(self): self._stop_flag = True def run(self): while not self._stop_flag: # 执行任务 ``` #### 四、线程同步机制 1. **互斥锁(Lock)** 防止多个线程同时修改共享资源: ```python lock = threading.Lock() with lock: # 临界区代码 ``` 2. **信号量(Semaphore)** 控制同时访问资源的线程数量: ```python sem = threading.Semaphore(3) # 允许3个线程同时访问 ``` 3. **事件(Event)** 线程间通信的简单方式: ```python event = threading.Event() event.wait() # 阻塞直至事件被触发 event.set() # 触发事件 ``` #### 五、线程通信 推荐使用线程安全的`Queue`队列[^3],典型生产者-消费者模型: ```python from queue import Queue q = Queue(maxsize=10) # 生产者 q.put(item) # 消费者 item = q.get() ``` #### 六、GIL的影响与应对策略 - **GIL机制**:Python解释器在同一时间仅允许一个线程执行字节码[^2] - **优化建议**: - IO密集型:多线程可显著提升效率 - 计算密集型:改用多进程(`multiprocessing`模块) #### 七、应用场景 1. **Web服务器处理并发请求** 2. **GUI程序保持界面响应** 3. **批量文件下载/处理** #### 八、注意事项 1. 避免过度使用全局变量[^3] 2. 优先使用`Queue`进行数据传递 3. 使用`ThreadPoolExecutor`管理线程池: ```python from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers=4) as executor: executor.map(task, args_list) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值