线程如何关闭子线程程还会存在么

Thinking in Java---如何正确的终止子线程
在进行多线程的时候,我们经常会启动多个子线程来完成一个任务,那么如何在任务完成的时候或则是在任务进行到一定程度的时候正确的退出这些线程就成了一个问题。下面就对这个问题进行一些探讨。
一.无阻塞任务的终止
无阻塞任务的终止是最简单的情况,在这种情况下我们可以通过设定run()方法中while()循环结束的条件来很容易的正确终止这个任务。下面的这段代码演示了一般终止一个非阻塞任务的方法:
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import java.util.concurrent.TimeU
/**同时启动三个线程对一个计数对象进行计数,
* 当计数达到一百时所有子线程终止*/
class Count{
private int count=0;
//自增函数
public synchronized void increment(){
public synchronized int get(){
class Task implements Runnable{
//用于判断任务是否要结束的标志,是一个需要被多个线程读取的变量
//所以用volatile关键进行修饰;volatile可以保证变量的可视性
//即当任意一个任务修改了canceled后,该变化对所有任务都是可见的
public static volatile boolean canceled=
//修改canceled标志
static void cancel(){
public Task(Count count){
this.count=
//每个任务都会不断调用count的increment()方法,当值等于100时所有任务退出
public void run(){
while(!canceled){
//必须保证下面这段代码是原子操作,才能在恰好加到100时正确的退出
synchronized(count){ //这个同步块不加会出问题
if(canceled){ //加了上面的同步后,不加这句话也会出问题
count.increment();
System.out.println(count.get());
if(count.get()==100){
TimeUnit.MILLISECONDS.sleep(100);
}catch(InterruptedException ex){
System.out.println(&中断&);
public class Test {
public static void main(String[] args) throws Exception{
ExecutorService exec =Executors.newCachedThreadPool();
Count count = new Count();
//同时启动三个线程来进行计数
for(int i=0;i&3;i++){
exec.execute(new Task(count));
exec.shutdown();
TimeUnit.SECONDS.sleep(4);
System.out.println(count.get());
System.exit(1);
这段代码非常简单,但是要注意我们在while循环所加的那个同步块,如果那个地方不进行同步,那么虽然count对象的increment方法和get()方法都是同步的,但是由于这两条语句之间还是会存在切换,从而会导致大多数情况下都不能在恰好达到100时结束任务;同时还要注意在每次递增之前也会有一个检查,这也是必要的,原因在于虽然有线程将canceled标志置为true,但是当前线程却可能刚从阻塞状态苏醒,需要执行下面所有的语句以后才会去检查是否退出while循环,这样就会造成多加的问题。
二.阻塞任务的终止
与非阻塞的任务相比,终止一个阻塞的任务要棘手的多。当一个线程阻塞的时候,是停在run()方法中的某一点的,我们就不能像上面那样通过程序自己检查某个条件而自动跳出循环了。我们可以大致把阻塞分为三类:调用sleep()引起的阻塞,等待同步锁引起的阻塞,等待某种资源引起的阻塞(如IO阻塞)。Thread类提供了一个interrupt()方法,用于中断阻塞;这个方法有两种作用,第一是产生一个InterruptedException,另一个是重置线程的interrupted状态为true。但是可惜的是这种中断只对sleep()引起的阻塞是有效的,对其它两种阻塞是无效的。下面的代码示范了这点。
import java.io.IOE
import java.io.InputS
import java.net.ServerS
import java.net.S
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import java.util.concurrent.F
import java.util.concurrent.TimeU
/**演示通过试图通过Thread.interrupt()方法终止三种不同阻塞的情形*/
//调用sleep而引起的阻塞
class SleepBlocked implements Runnable{
public void run(){
TimeUnit.SECONDS.sleep(1000);
}catch(InterruptedException e){
System.out.println(&InterruptedException&);
System.out.println(&Exiting SleepBlocked.run()&);
class IOBlocked implements Runnable{
private InputS
public IOBlocked(InputStream is){
public void run(){
System.out.println(&waiting for read(): &);
in.read();
}catch(IOException e){
if(Thread.currentThread().isInterrupted()){
System.out.println(&Interrupted from blocked I/O&);
throw new RuntimeException();
System.out.println(&Exiting IOBlocked.run()&);
//同步锁阻塞
class SynchronizedBlocked implements Runnable{
public synchronized void f(){
while(true){
Thread.yield();
public SynchronizedBlocked(){
new Thread(){ //启动一个线程,通过执行永不退出的同步方法获取自身的锁
public void run(){
}.start();
public void run(){
System.out.println(&Trying to call f()&);
System.out.println(&Exiting synchronizedBlocked.run()&);
public class Interrupting {
private static ExecutorService exec = Executors.newCachedThreadPool();
public static void test(Runnable r) throws InterruptedException{
Future f =exec.submit(r);
TimeUnit.MICROSECONDS.sleep(100);
System.out.println(&Interrupting &+r.getClass().getName());
f.cancel(true); //通过cancel()方法结束线程
System.out.println(&Interrupt sent to &+r.getClass().getName());
public static void main(String[] args) throws Exception{
ExecutorService exec = Executors.newCachedThreadPool();
ServerSocket server = new ServerSocket(8080);
InputStream socketInput
= new Socket(&localhost&,8080).getInputStream();
exec.execute(new IOBlocked(socketInput));
exec.execute(new IOBlocked(System.in));
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(&Shutting down all threads&);
exec.shutdownNow(); //向所有线程发送interrupted信息
TimeUnit.SECONDS.sleep(1);
System.out.println(&Closing &+socketInput.getClass().getName());
socketInput.close(); //通过关闭底层阻塞的IO而结束线程
TimeUnit.SECONDS.sleep(1);
System.out.println(&Closing &+System.in.getClass().getName());
System.in.close();
System.out.println(&end&);
waiting for read():
waiting for read():
Shutting down all threads
Closing java.net.SocketInputStream
Interrupted from blocked I/O
Exiting IOBlocked.run()
Closing java.io.BufferedInputStream
从输出来看确实只有sleep()造成的阻塞通过调用Thread.interrupt()方法中断了,而其余两种阻塞并不能并不能通过这种方法中断。但是也有例外的情况
1)对于IO阻塞,如上所示我们肯定可以通过自己关闭底层的IO资源来中断阻塞;但是如果我们使用的是nio,那么它可以自动响应interrupt()中断。如下面的代码所示:
import java.io.IOE
import java.net.InetSocketA
import java.net.ServerS
import java.nio.ByteB
import java.nio.channels.AsynchronousCloseE
import java.nio.channels.ClosedByInterruptE
import java.nio.channels.SocketC
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import java.util.concurrent.F
import java.util.concurrent.TimeU
/**演示如何通过interrupt中断nio造成的阻塞*/
class NIOBlocked implements Runnable{
private final SocketC
public NIOBlocked(SocketChannel sc){
public void run(){
System.out.println(&Waiting for read() in&+this);
sc.read(ByteBuffer.allocate(1)); //此处阻塞
}catch(ClosedByInterruptException e){
System.out.println(&ClosedByInterruptedException&);
}catch(AsynchronousCloseException e){
System.out.println(&AsynchronousCloseException&);
}catch(IOException e){
throw new RuntimeException(e);
System.out.println(&Exiting NIOBlocked.run()&+this);
public class NIOInterruption {
public static void main(String[] args) throws IOException, InterruptedException{
ExecutorService exec = Executors.newCachedThreadPool();
ServerSocket server = new ServerSocket(8080);
InetSocketAddress isa = new InetSocketAddress(&localhost&,8080);
SocketChannel sc1 = SocketChannel.open(isa);
SocketChannel sc2=SocketChannel.open(isa);
Future f = exec.submit(new NIOBlocked(sc1));
exec.execute(new NIOBlocked(sc2));
exec.shutdown();
//exec.shutdownNow()//通过这一句就可以正确的结束掉子线程
TimeUnit.SECONDS.sleep(1);
//下面通过两种方法
f.cancel(true);//通过产生InterruptedException中断来退出阻塞
TimeUnit.SECONDS.sleep(1);
sc2.close(); //通过手动关闭底层资源来中断阻塞,与上面的方法形成对比
Waiting for read() inlkl.NIOBlocked@3801167a
Waiting for read() inlkl.NIOBlocked@601a013b
ClosedByInterruptedException
Exiting NIOBlocked.run()lkl.NIOBlocked@601a013b
AsynchronousCloseException
Exiting NIOBlocked.run()lkl.NIOBlocked@3801167a
2).对于同步锁阻塞,如果我们使用的是ReentrantLock,那么也是可以被中断的,这与synchronized造成的阻塞不一样。下面的代码示范了这一点:
import java.util.concurrent.TimeU
import java.util.concurrent.locks.ReentrantL
/**展示使用ReentrantLock锁定而导致的阻塞,
* 可以使用Thread.interruted()方法退出*
class BlockedMutex{
private ReentrantLock lock = new ReentrantLock();
public BlockedMutex(){
lock.lock(); //在构造器中给自己加锁,并且不进行释放
public void f(){
//当本线程获得锁时,该方法立即返回
lock.lockInterruptibly();
System.out.println(&lock acquired in f()&);
}catch(InterruptedException ex){
System.out.println(&Interrupted from lock acquisition in f()&);
class Blocked2 implements Runnable{
BlockedMutex blocked = new BlockedMutex();
public void run(){
System.out.println(&Waiting for f() in BlockedMutex&);
blocked.f();
System.out.println(&Broken out of blocked call&);
public class Interrupting2 {
public static void main(String[] args)throws Exception{
Thread t = new Thread(new Blocked2());
t.start();
TimeUnit.SECONDS.sleep(1);
System.out.println(&t.interrupted&);
t.interrupt();
Waiting for f() in BlockedMutex
t.interrupted
Interrupted from lock acquisition in f()
Broken out of blocked call
三.一种统一的表示
我们知道Thread的interrupt()方法不但可以抛出InterruptedException的异常,也可以重置线程的中断状态,我们可以通过interrupted()来检查是否调用过interrupt()方法;因此我们可以通过检查interrupted()的状态来决定是否退出子线程;这种方式可以和捕捉中断异常的方法结合使用。值得注意的是被设计为响应interrupt()的类,必须要有相应的资源清理机制。如下面的代码所示:
import java.util.concurrent.TimeU
/**使用线程的Interrupted()来控制子线程的结束*/
//模拟一个需要进行分配以后就要进行清理的对象
class NeedsCleanup{
public NeedsCleanup(int ident){
System.out.println(&NeedsCleanup &+id);
public void cleanup(){
System.out.println(&Cleaning up &+ id);
class Blocked3 implements Runnable{
private volatile double d=0.0;
public void run(){
while(!Thread.interrupted()){
//对于这种需要进行清理的对象,其后必须要紧跟try finally语句
NeedsCleanup clean1 = new NeedsCleanup(1);
System.out.println(&Sleeping&);
TimeUnit.MILLISECONDS.sleep(500);
NeedsCleanup clean2 = new NeedsCleanup(2);
System.out.println(&Calculating&);
for(int i=1; i&; i++){
d=d+(Math.PI+Math.E)/d;
System.out.println(&Finished time-consuming operation&);
clean2.cleanup();
clean1.cleanup();
System.out.println(&Exiting by Interrupted&);
}catch(InterruptedException ex){
System.out.println(&Exiting by InterruptedException&);
public class Interrupted3 {
public static void main(String[] args) throws Exception{
Thread t = new Thread(new Blocked3());
t.start();
TimeUnit.MILLISECONDS.sleep(1800);
t.interrupt();
NeedsCleanup 1
NeedsCleanup 2
Calculating
Finished time-consuming operation
Cleaning up 2
Cleaning up 1
Exiting by Interrupted
总之使用Thread.interrupted()来控制线程的循环是一个不错的选择,因为我们总是可以通过Thread.interrupt()来终止这种线程(包括ExecutorService的shutdownNow()方法和线程的cancel()方法);如果线程中没有循环只有阻塞,那么大多数情况下,Thread.interrupt()也可以中断这些阻塞并抛出InterruptedException异常。没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
家境小康, 积分 1117, 距离下一级还需 883 积分
论坛徽章:0
子线程先退出的代码
#include &pthread.h&
#include &stdio.h&
void* thrd_start_routine(void* v)
{
& & sleep(5);
& & & & printf(&created thread\n&);
}
int main()
{
& & & & pthread_
& & & & & & & &
& & & & pthread_create(&thrdid, NULL, &thrd_start_routine, NULL);
& & & &
& & & & sleep(10);
& & & & printf(&main thread\n&);
& & & &
& & & & return&&0;
}
复制代码
created thread
//又过了5秒
main thread
主线程先退出
#include &pthread.h&
#include &stdio.h&
void* thrd_start_routine(void* v)
{
& & sleep(10);
& & & & printf(&created thread\n&);
}
int main()
{
& & & & pthread_
& & & & & & & &
& & & & pthread_create(&thrdid, NULL, &thrd_start_routine, NULL);
& & & &
& & & & sleep(5);
& & & & printf(&main thread\n&);
& & & &
& & & & return&&0;
}
复制代码
main thread
//退出了,切换到了shell,而且在5秒内用ps -ef | grep thread(编译生成的程序)也看不到了
是主线程退出时,子线程会强制退出吗?
小富即安, 积分 4153, 距离下一级还需 847 积分
论坛徽章:0
家境小康, 积分 1877, 距离下一级还需 123 积分
论坛徽章:1
当然也有方法实现主线程先退出,子线程继续
论坛徽章:0
原帖由 chenzhanyiczy 于
14:15 发表
当然也有方法实现主线程先退出,子线程继续
如何做呢?
大富大贵, 积分 16071, 距离下一级还需 3929 积分
论坛徽章:0
主线程退出,进程也就结束了,系统回收所有资源
子线程还能继续运行?
家境小康, 积分 1117, 距离下一级还需 883 积分
论坛徽章:0
又写了两个测试代码
1:主进程退出时,不管子、孙...线程都会退出
& &&&但是不是创建子线程的线程退出,被他创建的子线程都要退出,比如下面的的子线程创建了孙线程,子线程先退出,孙线程没有退出
#include &pthread.h&
#include &stdio.h&
void* fun(void* v)
{
& & sleep(10);
& & printf(&grandson thread\n&);
}
void* thrd_start_routine(void* v)
{
& & pthread_
& & pthread_create(&thd, NULL, &fun, NULL);
& & sleep(5);
& & & & printf(&created thread\n&);
}
int main()
{
& & & & pthread_
& & & & & & & &
& & & & pthread_create(&thrdid, NULL, &thrd_start_routine, NULL);
& & & &
& & & & sleep(15);
& & & & printf(&main thread\n&);
& & & & return&&0;
}
复制代码
created thread
//又过了5秒
grandson thread
//又过了5秒
main thread
------------------------------------------------------------------------------------------
2:如果主线程调用了pthread_exit,那么它退出了,子线程也不会推出
#include &pthread.h&
#include &stdio.h&
void* thrd_start_routine(void* v)
{
& & sleep(10);
& & & & printf(&created thread\n&);
}
int main()
{
& & & & pthread_
& & & & & & & &
& & & & pthread_create(&thrdid, NULL, &thrd_start_routine, NULL);
& & & &
& & & & sleep(5);
& & & & printf(&main thread\n&);
& & & & pthread_exit(NULL);
& & & & return&&0;
}
复制代码
main thread
//又过了5秒
created thread
大富大贵, 积分 16071, 距离下一级还需 3929 积分
论坛徽章:0
TO: zhongyj
我在想,你测试的结果之所以是这样,会不会是因为线程sleep阻塞住了。这样线程无法退出,只能等sleep时间到了之后再退出?
小富即安, 积分 4153, 距离下一级还需 847 积分
论坛徽章:0
同楼上一样想法。。。。
家境小康, 积分 1117, 距离下一级还需 883 积分
论坛徽章:0
回复 #7 雨过白鹭洲 的帖子
你是说的上面的
2:如果主线程调用了pthread_exit,那么它退出了,子线程也不会推出
这个代码和上面主线程先退出,子线程也退出的代码只是多了句pthread_exit(NULL);
而且sleep的时间差了5秒,不会阻塞这么长时间吧
大富大贵, 积分 16071, 距离下一级还需 3929 积分
论坛徽章:0
回复 #9 zhongyj 的帖子
子线程退出,孙线程不会退出这个很好理解
但主线程退出时调用pthread_exit,然后你仍然return 0,这时候主线程应该还是退出了,按照main的定义,进程就应该结束了
我查一下pthread_exit先。。
北京盛拓优讯信息技术有限公司. 版权所有 京ICP备号 北京市公安局海淀分局网监中心备案编号:22
广播电视节目制作经营许可证(京) 字第1234号
中国互联网协会会员&&联系我们:
感谢所有关心和支持过ChinaUnix的朋友们
转载本站内容请注明原作者名及出处下次自动登录
现在的位置:
& 综合 & 正文
java主线程结束和子线程结束之间的关系
最近在和同事讨论java主线程和子线程之间的关系,自己也到网上搜索了下,发现各种答案都有,有些还是互相矛盾的。经过测试自己得出以下几个结论,跟大家分享下,如果有错误,欢迎大牛指正,帮助我这只小菜鸟。废话不多说,直接上结论:
(一)Main线程是个非守护线程,不能设置成守护线程。
这是因为,main线程是由java虚拟机在启动的时候创建的。main方法开始执行的时候,主线程已经创建好并在运行了。对于运行中的线程,调用Thread.setDaemon()会抛出异常Exception in thread "main"
java.lang.IllegalThreadStateException。测试代码如下:
public class MainTest
public static void main(String[] args)
System.out.println(" parent thread begin ");
Thread.currentThread().setDaemon(true);
(二)Main线程结束,其他线程一样可以正常运行。
主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他线程一样可以正常执行。代码如下:
public class ParentTest
public static void main(String[] args)
System.out.println("parent thread begin ");
ChildThread t1 = new ChildThread("thread1");
ChildThread t2 = new ChildThread("thread2");
t1.start();
t2.start();
System.out.println("parent thread over ");
class ChildThread extends Thread
private String name =
public ChildThread(String name)
this.name =
public void run()
System.out.println(this.name + "--child thead begin");
Thread.sleep(500);
catch (InterruptedException e)
System.out.println(e);
System.out.println(this.name + "--child thead over");
--程序运行结果如下:
parent thread begin
parent thread over
thread2--child thead begin
thread1--child thead begin
thread2--child thead over
thread1--child thead over
这样其实是很合理的,按照操作系统的理论,进程是资源分配的基本单位,线程是CPU调度的基本单位。对于CPU来说,其实并不存在java的主线程和子线程之分,都只是个普通的线程。进程的资源是线程共享的,只要进程还在,线程就可以正常执行,换句话说线程是强依赖于进程的。也就是说,线程其实并不存在互相依赖的关系,一个线程的死亡从理论上来说,不会对其他线程有什么影响。
(三)Main线程结束,其他线程也可以立刻结束,当且仅当这些子线程都是守护线程。
java虚拟机(相当于进行)退出的时机是:虚拟机中所有存活的线程都是守护线程。只要还有存活的非守护线程虚拟机就不会退出,而是等待非守护线程执行完毕;反之,如果虚拟机中的线程都是守护线程,那么不管这些线程的死活java虚拟机都会退出。测试代码如下:
public class ParentTest
public static void main(String[] args)
System.out.println("parent thread begin ");
ChildThread t1 = new ChildThread("thread1");
ChildThread t2 = new ChildThread("thread2");
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
System.out.println("parent thread over ");
class ChildThread extends Thread
private String name =
public ChildThread(String name)
this.name =
public void run()
System.out.println(this.name + "--child thead begin");
Thread.sleep(500);
catch (InterruptedException e)
System.out.println(e);
System.out.println(this.name + "--child thead over");
执行结果如下:
parent thread begin
parent thread over
thread1--child thead begin
thread2--child thead begin
在这种情况下,的却主线程退出,子线程就立刻结束了,但是这是属于JVM的底层实现机制,并不是说主线程和子线程之间存在依赖关系。
我的博客参考了http://bbs.csdn.net/topics/这篇文章中大家的一些交流,碰撞出真知,大家的智慧是无穷的。
【上篇】【下篇】大神进!什么是线程池?线程运行完后会自己关闭吗?【易语言吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:149,483贴子:
大神进!什么是线程池?线程运行完后会自己关闭吗?收藏
如果用多线程处理队列里的节点算是线程池吗?多线程处理完后,这个线程会不会自己关闭,还是说要人为关闭,在线程最后强制结束自己行吗?如果有很多无用线程会不会白占内存导致程序崩溃?
线程返回后自己就结束了,不到万不得已不要轻易强制结束线程
线程是操作系统管理的资源,操作系统会维护一定的资源,最典型的表现操作系统会以一个数据结构表示。正常的线程在运行结束后,操作系统会适当掉这个线程相应的资源。但是线程生成和释放相对来说都比较慢。上面的线程要与特定的任何对应起来,你生成一个线程,它要指定特定的线程入口函数。当这个函数执行完成返回后,这个线程也就结束生命了。所以需要释放掉。线程池技术,将任务与线程分离开。线程大致工作原理如下当你需要以线程方式运行某个函数时,这是生成一个任务(说通俗点就是原来的线程,只是现在换一种形式表示)有个任务队列管理这些任务。从任务队列里去一个任务,线程调用这个任务的入口函数执行,当函数返回以后,即表示这个任务执行完成。这是这个任务会按照一定方法被销毁(任务可能会消耗一定资源,最基本的表示这个任务的数据结构)。而调用这个任务的线程并不会被销毁,它会继续调用下一个任务,依次类推。当任务队列里所有任务都被处理完之后,线程进入休眠状态,当又有新的任务时,在唤醒线程处理任务。
而线程池则管理多个线程。线程池根据一套策略管理多少个线程。最简单的线程池就是生成一组线程,简单的维护哪些线程处于空闲休眠状态。哪些线程正在运行哪个任务。负责的线程池可以根据各种策略做很多事情。例如当空闲线程不足时,再批量生成多个空闲线程;当有很多空闲线程时,批量销毁一些线程;根据计算机有多少处理器、多少个核心,确定线程池中线程数量的上限和下限;对任务和线程赋予执行优先级等等
登录百度帐号

我要回帖

更多关于 python 关闭子线程 的文章

 

随机推荐