关于join() 是否会释放锁的一些思考

关于join() 是否会释放锁的一些思考

# 首先从一个很有意思的问题开始:

  - 问 : Thread 的join() 方法是否会释放锁?

  - 答: 会!

# 如果一切到这里就结束了,那可能也就没有这篇小记了,但是我的脑子却冒出了一些奇怪的想法:

  - 释放哪个对象的锁呢?

  - 难道是释放父线程所持有的所有对象的锁?

  -- 其实如果看了源码,很容易明白释放的是运行(这个地方可能有些歧义,但是我也不知道怎么说最好)join()方法的那个线程对象的锁,不过这些都是后话,我们且往下看;

# 然后我就写了代码来验证一下我的猜想, 代码如下:

复制代码

public class QQ {

    public static void main(String[] args) {
        Object oo = new Object();

        Thread thread1 = new MyThread("thread1 -- ", oo);
        thread1.start();

        synchronized (oo) {
            for (int i = 0; i < 100; i++) {
                if (i == 20) {
                    try {
                        thread1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " -- " + i);
            }
        }
    }

}

class MyThread extends Thread {

    private String name;
    private Object oo;

    public MyThread(String name, Object oo) {
        this.name = name;
        this.oo = oo;
    }

    @Override
    public void run() {
        synchronized (oo) {
            for (int i = 0; i < 100; i++) {
                System.out.println(name + i);
            }
        }
    }

}

复制代码

  - 运行一下,输出到 main -- 19 的时候,卡住了。

  - 接下来我们来寻找卡住的原因;

  -- 先使用jps找到出问题程序的进程号

  -- jstack pid 来查看线程堆栈,结果如下图

复制代码

"Thread-1" #14 prio=5 os_prio=0 tid=0x0000000018fa9000 nid=0x3f80 waiting for monitor entry [0x0000000019b0f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        - waiting to lock <0x00000000d8a06298> (a java.lang.Object)


"main" #1 prio=5 os_prio=0 tid=0x000000000228e800 nid=0x3d6c in Object.wait() [0x00000000028af000]
   java.lang.Thread.State: WAITING (on object monitor)
        - locked <0x00000000d8a06298> (a java.lang.Object)

复制代码

  -- 上图中我删掉了很多东西,只留下了一些关键的部分;首先我们看到 Thread-1 和 main 都在 waiting 状态,然后再注意到 Thread-1 在等待锁 <0x00000000d8a06298>,

但是main持有锁<0x00000000d8a06298>, 这又是什么情况呢? 难道 main 没有释放锁?

  - 这时候我们就回到了最初的问题,到底join()的时候释放的是谁的锁,通过查看join()方法的源码,很容易看到,其实调用的是 this.wait(),也就是说释放的是Thread-1 这个对象的锁

# 接着我们来用下面的代码证实一下我们得出的结论

复制代码

public class QQ {

    public static void main(String[] args) {
        Object oo = new Object();

        Thread thread1 = new MyThread("thread1 -- ", oo);
        thread1.start();

        synchronized (thread1) {
            for (int i = 0; i < 100; i++) {
                if (i == 20) {
                    try {
                        thread1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " -- " + i);
            }
        }
    }

}

class MyThread extends Thread {

    private String name;
    private Object oo;

    public MyThread(String name, Object oo) {
        this.name = name;
        this.oo = oo;
    }

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                System.out.println(name + i);
            }
        }
    }

}

复制代码

  - 很容易验证我们的猜想和理解是正确的

# 再接下来我们看一下如果调用wait() 方法,应该是怎么个情况呢;

 

复制代码

public class QQ {

    public static void main(String[] args) {
        Object oo = new Object();

        Thread thread1 = new MyThread("thread1 -- ", oo);
        thread1.start();

        synchronized (oo) {
            for (int i = 0; i < 100; i++) {
                if (i == 20) {
                    try {
                        oo.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + " -- " + i);
            }
        }
    }

}

class MyThread extends Thread {

    private String name;
    private Object oo;

    public MyThread(String name, Object oo) {
        this.name = name;
        this.oo = oo;
    }

    @Override
    public void run() {
        synchronized (oo) {
            for (int i = 0; i < 100; i++) {
                System.out.println(name + i);
            }
            oo.notifyAll();
        }
    }

}

复制代码

  - 乍一看,和调用join() 方法的现象一样; 

  - 嗯。。。有点意思了。。。

 

# 最后补充一点:jstack中的Thread-1 和 我们自己定义的 thread1 是不一样的,如果想要在jstack中显示我们自己定义的线程名, 则需要调用Thread的setName()方法

 

转载自:https://www.cnblogs.com/lwmp/p/11805440.html

<think>好的,我现在需要向用户解释Python的全局解释器(GIL)。首先,我得确保自己正确理解GIL的概念和作用。GIL是Python中的一个机制,尤其是在CPython解释器中,用来同步线程的执行。我记得它的主要作用是确保同一时刻只有一个线程执行Python字节码,这有助于简化内存管理,但也可能影响多线程程序的性能。 接下来,我需要考虑用户可能的背景。用户可能有一定的编程经验,但对Python内部机制不太熟悉。所以,解释时要避免过于技术化,用通俗易懂的语言。同时,用户可能想知道GIL存在的原因、带来的影响以及如何应对这些问题。 首先,我应该从GIL的定义开始,明确它是什么。然后解释为什么Python需要GIL,比如因为CPython的内存管理不是线程安全的,GIL通过阻止多线程同时执行来防止竞争条件。这里要提到引用计数等细节,但不要过于深入,保持简洁。 接下来,要讨论GIL带来的影响。特别是多线程程序在CPU密集型任务上的性能问题,比如多线程并不能充分利用多核CPU,甚至可能比单线程更慢。但同时,对于I/O密集型任务,GIL的影响较小,因为线程在等待I/O时会释放GIL,允许其他线程运行。这里可能需要举例子,比如网络请求或文件操作属于I/O密集型,而数值计算属于CPU密集型。 然后,要探讨绕过或替代GIL的方法。比如使用多进程代替多线程,每个进程有自己的解释器和内存空间,这样就能利用多核。或者使用其他Python实现如Jython或IronPython,它们没有GIL。此外,可以提到用C扩展来处理性能关键部分,因为C扩展可以释放GIL。 还要考虑用户可能存在的疑问,比如为什么Python不移除GIL。这里需要解释历史原因和兼容性问题,移除GIL会破坏现有C扩展,而且实现起来复杂,影响单线程性能。虽然有过尝试,比如Python 3.2的GIL优化,但完全移除仍然困难。 最后,总结GIL的优缺点,并给出实际应用中的建议。比如在CPU密集型任务中使用多进程或C扩展,I/O密集型任务仍可用多线程。同时,提及异步编程模型如asyncio作为替代方案。 需要检查是否有遗漏点,比如GIL的具体工作机制,如何切换线程,以及不同Python版本对GIL的改进。另外,确保使用正确的术语,避免错误,比如区分CPython和其他实现。还要注意格式,数学表达式用$...$,独立公式用$$...$$,但当前内容可能不涉及数学公式,所以可能不需要。但如果有方程式,要正确应用格式。 另外,用户要求用中文回答,结构清晰,分点说明。可能需要分点列出GIL的作用、影响、解决方案等。使用例子帮助理解,比如多线程计算斐波那契数列的效率问题,或者多进程的multiprocessing模块。 需要确保内容准确,比如GIL确实存在于CPython,而其他实现如Jython没有。同时,在提到多进程时,指出其缺点,比如进程间通信的开销。还要提到concurrent.futures模块的使用,作为替代方案。 最后,检查是否有误解,比如GIL对多线程的影响是否绝对,或者是否所有Python程序都受GIL限制。确认GIL只影响CPU密集型多线程,而不影响I/O密集型,也不影响多进程。同时,确认Python中多线程依然适合I/O密集型任务,因为等待期间释放GIL。 总结思考后,组织内容结构,确保逻辑连贯,从定义、原因、影响、解决方案到总结,逐步展开,帮助用户全面理解GIL及其应对策略。</think>Python 的全局解释器(**Global Interpreter Lock, GIL**)是 CPython 解释器(Python 官方实现)中的一个关键机制,它对多线程编程有深远影响。以下是逐步解析: --- ### 1. **GIL 是什么?** - GIL 是一个**互斥**(Mutex),保证同一时刻只有一个线程能执行 Python 字节码。 - 无论 CPU 有多少核心,**多线程 Python 程序在 CPython 中无法并行执行**,只能交替执行(并发)。 --- ### 2. **为什么需要 GIL?** - **简化内存管理**:Python 使用引用计数管理对象生命周期。若多线程同时修改引用计数,可能导致内存错误。GIL 通过单线程执行避免竞争条件。 - **保护 C 扩展兼容性**:许多 Python C 扩展依赖 GIL 的线程安全假设,移除 GIL 会破坏现有生态。 --- ### 3. **GIL 的影响** #### ✅ 优点 - 简化单线程程序的实现。 - 提高单线程性能(避免多线程竞争开销)。 #### ❌ 缺点 - **CPU 密集型任务性能瓶颈**:多线程无法利用多核加速。例如: ```python # 计算斐波那契数列的多线程代码并不会更快 import threading def compute(): result = 0 for _ in range(10**7): result += 1 # 两个线程同时执行 t1 = threading.Thread(target=compute) t2 = threading.Thread(target=compute) t1.start(); t2.start() t1.join(); t2.join() ``` 由于 GIL,两个线程实际是交替执行,总耗时可能接近单线程的两倍。 - **I/O 密集型任务影响较小**:线程在等待 I/O(如网络请求、文件读写)时会主动释放 GIL,其他线程可继续执行。 --- ### 4. **如何绕过 GIL?** #### 方法 1:使用多进程 - 每个进程有独立的 Python 解释器和内存空间,可绕过 GIL。 - 通过 `multiprocessing` 模块或 `concurrent.futures.ProcessPoolExecutor` 实现并行。 ```python from multiprocessing import Pool def compute(n): return sum(range(n)) with Pool(4) as p: # 4 进程并行 print(p.map(compute, [10**7]*4)) ``` #### 方法 2:使用其他 Python 实现 - **Jython/IronPython**:基于 JVM/.NET 的实现,无 GIL,但生态兼容性差。 - **PyPy**:通过 JIT 优化性能,但仍保留 GIL。 #### 方法 3:C/C++ 扩展 - 在 C 扩展中手动释放 GIL:使用 `Py_BEGIN_ALLOW_THREADS` 和 `Py_END_ALLOW_THREADS` 宏。 - 常用库如 NumPy、Cython 已在底层规避 GIL。 --- ### 5. **GIL 的未来** - **短期内不会消失**:移除 GIL 需重写 CPython 内存管理并破坏现有 C 扩展,代价巨大。 - **优化方向**:Python 3.2+ 通过更细粒度的(如 `PyMutex`)减少 GIL 的负面作用,但未根治问题。 --- ### 总结 - **适用场景**: - 多线程适合 I/O 密集型任务(如网络爬虫)。 - 多进程或异步编程(如 `asyncio`)适合 CPU 密集型任务。 - **开发建议**:优先使用多进程、异步编程或 C 扩展处理高性能需求,而非依赖多线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值