问题

如何学会多线程工作?

回答
要真正掌握多线程编程,这可不是一蹴而就的事,它更像是在数字世界的操作系统里,学会如何同时扮演好几个角色,并且让它们之间协作顺畅,各司其职。这玩意儿,一旦玩溜了,你的程序就能跑得飞快,响应也更及时,用户体验那叫一个“丝滑”。

首先,咱得明白,什么是线程?你可以把你的整个程序想象成一个大车间,而线程,就是这个车间里一个个独立工作的工人。一个工人(线程)在做一件事情,比如烧水,另一个工人(线程)可以同时在整理工具,第三个工人(线程)还能在记录产量。它们都是在同一时间,为了同一个大目标(完成整个程序的功能)而努力。

为什么需要多线程?

想象一下,你的程序要做个大事,比如下载一个大文件,同时还要让你能够继续在界面上滑动,或者处理一些用户输入。如果你的程序只有一个工人(单线程),那么它下载文件的时候,就顾不上处理你的滑动和输入了,你的界面会卡死,用户体验直线下降。但有了多个工人(多线程),一个工人专门负责下载,另一个工人负责响应用户界面,它们就能并行工作,互不干扰,你的程序就能保持响应,流畅运行。

学会多线程,得从这几个方面入手:

1. 理解核心概念,建立“心法”:

进程 vs. 线程: 这个得先弄清楚。进程就像一个独立的公司,有自己的办公场地、资源(内存、文件句柄等)。而线程,就是这家公司里的员工。一个公司里可以有很多员工(线程),他们共享公司里的资源,协同工作。但如果公司(进程)倒闭了,里面的员工(线程)也就没了。所以,线程的开销比进程小得多,创建和销毁都更快。
并发 vs. 并行: 这俩词儿也容易混。
并发 (Concurrency) 就像你一个人在厨房里,虽然只有一个锅,但你可以同时切菜、烧水、炒菜,通过快速切换任务,让它们看起来像是在同时进行。即使你只有一个CPU核心,也能实现并发。
并行 (Parallelism) 则是真的有多个厨师(多个CPU核心),他们每个人都在独立地同时做不同的菜。这才是真正的“同时”工作。多线程编程的目标之一就是实现并行,但即使没有多核CPU,也能通过并发让程序在用户感知上更流畅。
线程生命周期: 线程也不是生下来就能干活的,它也有个“成长过程”。一般有:
新建 (New): 刚创建的线程,还没开始运行。
可运行 (Runnable): 线程已经准备好了,可以被CPU调度执行。
运行 (Running): 线程正在CPU上执行代码。
阻塞 (Blocked/Waiting): 线程因为某些原因暂停执行,比如等待I/O完成、等待锁释放。
终止 (Terminated): 线程执行完毕或者异常退出。

2. 掌握实现“工具”:

不同的编程语言,实现多线程的方式略有不同,但原理是相通的。

Java: Java提供了`Thread`类和`Runnable`接口。
继承Thread类: 创建一个类继承`Thread`,然后重写`run()`方法,里面写线程要执行的代码。
实现Runnable接口: 创建一个类实现`Runnable`接口,实现`run()`方法。然后创建一个`Thread`对象,将`Runnable`对象作为构造函数参数传入。这种方式更灵活,因为Java不支持多重继承,但可以实现多个接口。
ExecutorService: 这是Java中最推荐的多线程管理方式。它提供了一个线程池,可以复用已创建的线程,避免了频繁创建和销毁线程的开销。你可以提交任务给`ExecutorService`,它会负责线程的调度和管理。
Python: Python的线程实现相对特殊,因为有一个叫做GIL (Global Interpreter Lock) 的东西。
threading模块: Python的`threading`模块提供了线程相关的API。你可以创建`Thread`对象,并指定`target`函数为线程的入口点。
GIL的限制: GIL意味着在一个Python进程中,即使你有多个CPU核心,同一时刻也只有一个线程能真正执行Python字节码。所以,对于CPU密集型任务(比如大量的数学计算),Python的`threading`模块并不能带来真正的并行加速。但对于I/O密集型任务(比如网络请求、文件读写),线程会在等待I/O的时候释放GIL,让其他线程有机会执行,从而提高效率。
multiprocessing模块: 如果你想利用多核CPU进行并行计算,Python提供了`multiprocessing`模块,它创建的是独立的进程,每个进程都有自己的Python解释器和GIL, thus overcoming the GIL limitation for CPUbound tasks.
C++: C++的标准库提供了`std::thread`。
`std::thread`: 创建一个`std::thread`对象,将一个可调用对象(函数、lambda表达式等)作为构造函数参数。
`join()` 和 `detach()`: `join()` 方法会让主线程等待子线程执行完毕。`detach()` 方法会将子线程与主线程分离,允许子线程独立运行,但主线程不再能控制它,也无法知道它何时结束。
C: C 提供了`Thread`类,但更常用的是`Task`和`async/await`。
`Thread`类: 类似于Java,可以创建和管理`Thread`对象。
`Task` Parallel Library (TPL): `Task` API提供了一种更高级、更灵活的方式来表示异步操作,可以轻松地在多线程环境中执行代码。
`async/await`: 这是C实现异步编程的强大工具,可以让你编写看起来像同步代码的异步代码,有效避免线程阻塞。

3. 解决“协作”的难题:同步与互斥

多线程编程最让人头疼的就是数据竞争 (Data Race)。当多个线程同时访问和修改同一块共享数据时,如果不对访问进行控制,结果就会变得不可预测,就像一群工人同时抢着搬一块砖,最后不知道这块砖被搬到哪里去了。

为了解决这个问题,我们需要同步 (Synchronization) 机制。

互斥锁 (Mutex / Lock): 这是最基本的同步工具。想象一下,一个房间里只能一个人进去,那么其他人就得排队等待。当一个线程获得了锁,其他想访问共享资源的线程就必须等待,直到锁被释放。
`synchronized` 关键字 (Java): Java中,你可以用`synchronized`关键字修饰方法或代码块,它会自动加上一个内部锁。
`Lock` 接口 (Java): Java的`java.util.concurrent.locks`包提供了更灵活的锁机制,如`ReentrantLock`,允许更精细地控制锁的获取和释放,以及支持中断锁的获取。
`std::mutex` (C++): C++标准库提供了`std::mutex`,使用`lock()`和`unlock()`方法来保护共享资源。`std::lock_guard`和`std::unique_lock`是 RAII(Resource Acquisition Is Initialization)的封装,能自动管理锁的释放,避免忘记解锁的错误。
`lock` 关键字 (C): C也有`lock`关键字,用于创建互斥锁。
信号量 (Semaphore): 信号量比互斥锁更通用。它维护一个计数器,允许一定数量的线程同时访问资源。比如,一个餐厅有10个座位,那么最多允许10个人同时用餐,当有新客人来时,如果座位满了,就得等待。
`Semaphore` 类 (Java): Java提供了`Semaphore`类。
`System.Threading.Semaphore` (C): C也有对应的类。
条件变量 (Condition Variable): 条件变量通常与互斥锁一起使用,用于线程间的等待/通知机制。
线程A获得了锁,发现某个条件不满足,它就调用条件变量的`wait()`方法,这会将线程A挂起,并自动释放锁。
线程B执行了某些操作,使得线程A等待的条件满足了,它就调用条件变量的`notify()`或`notifyAll()`方法,唤醒一个或所有等待的线程A。
Java的`Condition`接口(配合`Lock`对象)和C++的`std::condition_variable`是常见的实现。
原子操作 (Atomic Operations): 对于一些简单的操作(如整数的加减、自增自减),可以使用原子操作。原子操作是指一次性、不可中断的操作。它们通常由硬件支持,效率很高。
Java的`java.util.concurrent.atomic`包提供了`AtomicInteger`、`AtomicLong`等类。
C++11引入了``头文件,提供了`std::atomic`模板。

4. 规避“陷阱”:常见的坑与最佳实践

死锁 (Deadlock): 这是多线程编程中最棘手的bug之一。当两个或多个线程互相等待对方释放锁时,就会发生死锁,程序就卡在那了,谁也无法继续。
例子: 线程A持有锁1,想获取锁2;线程B持有锁2,想获取锁1。此时,双方都永远等待下去。
避免方法:
按顺序获取锁: 规定所有线程获取锁的顺序,这样就不会出现循环等待。
超时机制: 在尝试获取锁时设置超时时间,如果超时就放弃,释放已持有的锁。
避免嵌套锁: 尽量不要让一个线程持有多个锁,如果必须,确保获取顺序一致。
活锁 (Livelock): 线程都在运行,但都在不断地尝试和重试,却始终无法完成工作,就像两个人互相礼让,结果谁都过不去。
线程饥饿 (Thread Starvation): 某个线程因为优先级较低,或者因为调度算法的问题,长时间得不到CPU资源,无法执行。
资源泄露: 线程没有正确关闭或释放资源,导致资源耗尽。
可重入性 (Reentrancy): 一个函数或方法在其被同一个线程多次调用时,不会产生问题。对于线程安全的代码,它应该是可重入的。
Java的`ReentrantLock`: 名字就说明了它的特性。
线程局部存储 (ThreadLocal Storage TLS): 允许每个线程拥有自己独立的变量副本,避免了多线程共享带来的问题。
Java的`ThreadLocal`类。
C++的`thread_local`关键字。
无锁编程 (LockFree Programming): 这是一个更高级的领域,通过特殊的算法和原子操作,在不使用锁的情况下实现线程安全。这能避免死锁等问题,并且在某些情况下能获得更高的性能,但实现起来非常复杂。

5. 实践出真知:动手去“玩”

理论讲得再多,不如自己动手敲代码。

从小处着手: 先从简单的例子开始,比如创建两个线程,让它们打印数字。
逐步增加复杂度: 尝试让线程之间传递数据,进行简单的计算。
引入同步机制: 故意制造数据竞争,然后用互斥锁、信号量等来解决。
模拟真实场景: 尝试写一个简单的网络爬虫,让多个线程同时下载网页;或者写一个简单的GUI应用,让后台线程处理耗时任务,主线程保持UI的响应。
使用调试工具: 学习使用IDE提供的调试器来跟踪线程的执行,观察变量的变化,这对于定位多线程bug至关重要。

总结一下,学会多线程工作,你需要:

1. 理解基础: 进程、线程、并发、并行这些基本概念要清晰。
2. 掌握工具: 熟悉你所用语言提供的线程创建和管理API。
3. 精通同步: 深刻理解锁、信号量、条件变量等同步机制,并知道何时何地使用它们。
4. 规避风险: 了解并能识别死锁、数据竞争等常见问题,并知道如何避免。
5. 勤于实践: 通过大量练习,将理论知识转化为实际能力。

多线程编程是一个充满挑战但又非常有价值的领域。刚开始可能会觉得有点烧脑,但别怕,一步一个脚印,多看、多想、多练,你会逐渐掌握这项“内功心法”,让你的程序脱胎换骨,跑得更欢快!

网友意见

user avatar

请参见同人于野老师的文章

一脑不能两用 « 学而时嘻之

一脑不能两用

我上初中的时候思想有点叛逆。那时候当然没有博客,但老师要求我们写“周记”。我用现在写博客的精神写周记,认为如果写的东西太平淡就没意思。比如老师说人不能一边看电视一边写作业,我就写了一篇周记,说一脑完全可以两用比如我就是专门一边看电视一边写作业。


我只所以到现在还记得这件事,是因为有一个反馈。我爸在家长会上听老师提到我的这篇周记,回来跟我说我允许你看电视你偷着乐也就算了以后不要公开说这种跟老师要求相反的言论。


这件事的要点是不管是我爸还是老师都没有批评我写作业的时候看电视。也就是说,也可能因为我作业都写对了,他们被我的周记说服了,认为我可能真的可以一脑两用。


很多人认为自己擅长一脑两用,很多人指导别人怎么一脑两用。很多人认为在现在这个世界中,不会一脑两用就没法工作。真正的牛人应该在中学的课堂上读完世 界名著。真正的牛人干活的时候都是同时开着 msn, qq, google talk,另一个窗口还读着一部小说。真正的牛人应该同时听两个下属汇报,手里还在起草下午会议的发言要点。这些是真正的牛人么?


然而科学事实是人脑的硬件结构决定了根本就不存在什么“一脑两用”。人脑不能并行计算。当我们以为我们在进行 multitasking 的时候,我们实际上是在 switch-tasking。我们的大脑像最土的CPU一样在不同任务之间“轮转”,而不能真正“同时”做这些任务。


这么一个看似简单的道理其实并不是那么显然的。人类可以说直到最近几十年,因为神经科学和认知科学的进步,才逐渐确信了这一点。参见《Mind Rules》这本书。


现在是最关键的部分了:Switch-tasking 是相当低效率的工作方式。《The Myth of Multitasking》这本书里面推荐了一个小实验。用笔在纸上写“Multitasking is worse than a lie” 这句话,但是要求写的时候每写一个字母就在这个字母后面写下一个数字,表明这是句子中的第几个字母。也就是说写出来的句子是:
M1 u2 l3 t4 i5 t6 a7 s8 k9 i10 n11 ……
看看这么写完需要多少时间。


然后再做实验的第二部分,先写下“Multitasking is worse than a lie.”这句话,然后再给这句话标注数字。结果纸上的东西跟前面完全一样,可是你使用的时间将会大大减少!


在完成实验的第一部分的时候,我们实际上是在句子和数字之间 switch-tasking. 实验的结果就是这种 switch-tasking 特别浪费时间。时间浪费到哪去了呢?当你在两个任务之间来回转化的时候,有一个时间成本,”switch cost”。


当你认为你在 multitasking 的时候,你实际上是在 switch-tasking,而 switch-tasking 有一个 switch cost,所以这是一个特别地效率的工作方式!


正确的做事方法是一心一意,同一时间只做一件事!


这时候肯定有人会问,我跑步的时候听评书,这个 multitasking 不是很有效率么?《The Myth of Multitasking》说,这个叫做 background tasking,后台任务。这里的关键是跑步不用动脑子。只要一动脑子,就成了 switch-tasking,就是低效率的。


Switch-tasking 除了自己的低效率之外,还有一个巨大的缺点,就是如果在你跟别人(下属或者家人)交流的时候这么做,心不在焉,是对人的伤害。不管你多忙,你都应该让跟你说话的人感到他是重要的。更何况这种没有质量的交流同样是低效率的。


统计表明一个人平均每小时会被干扰6次。被干扰的时间越长,就越不容易回来。有人研究表明,平均每个每周工作40小时的人,其每天(!)因为干扰而浪费的时间是2.1小时!责任越大,头衔越多的人,越需要被动的 switch-tasking,其工作效率也越低!


所以提高工作效率,一定要避免 switch-tasking. 我现在已经做到的包括:
- 不要像偏执狂一样每隔15分钟就检查信箱。
- 那些 MSN 之类的聊天工具实际上是玩具,对大多数干事的人来说毫无意义。聊天最好的办法是打电话。
- 把那些电子邮件程序中的来信自动提示功能都关了。


然而很多情况下是别人总找我们,树欲静而风不止,怎么办呢?这本书里提供了一些办法。


如果这个人每天需要找你十次,比如说你的秘书,最好的办法是跟她约定一个每天30分钟的会面时间。有些秘书每次离开老板办公室的时候总会下意识地低头停 顿一下,这是因为她需要考虑一下还有没有什么事情忘了说了 - 因为她不知道什么时候才能再次逮着老板说句话。一个固定的时间会把你和秘书都解放出来。


对于一般人,最好的办法也是给他们一个固定的期望。给一般员工一个固定的每周开会时间,会上随便说,会下别找我。


有些人的做法是除了一个固定时间之外一律不接电话,就连客户的电话也不接。但是他们的电话留言提示中会告诉对方我一定会在什么时间回复你的留言。一旦别人对你有了可靠的期望,这些人不会介意你不接电话。


专注是一种力量。智能手机不离手的不是真正的牛人。真正的牛人陪家人的时候就好好陪家人,跟朋友玩的时候就好好跟朋友玩,做事的时候就好好做事。哪怕是看电影,也应该全神贯注地看电影 - 如果是烂片干脆就别看。


老师,我错了。我多么希望当初你在我的周记上写下下面的批语:你不是 multitasking,你是 switch-tasking。你这么看电视是很低效率的,你没有真正好好欣赏那些电视节目。

类似的话题

  • 回答
    要真正掌握多线程编程,这可不是一蹴而就的事,它更像是在数字世界的操作系统里,学会如何同时扮演好几个角色,并且让它们之间协作顺畅,各司其职。这玩意儿,一旦玩溜了,你的程序就能跑得飞快,响应也更及时,用户体验那叫一个“丝滑”。首先,咱得明白,什么是线程?你可以把你的整个程序想象成一个大车间,而线程,就是.............
  • 回答
    知乎上推崇学习 Python 入行 IT 的现象确实非常普遍,这主要源于 Python 语言的易学性、广泛的应用领域以及当前 IT 行业的蓬勃发展。然而,正如任何职业发展路径一样,学习 Python 后找不到工作的情况并非不可能发生,而且背后的原因可能比初学者想象的要复杂。如果一个学完 Python.............
  • 回答
    大学生活,对很多人来说是一段崭新的人生篇章。在这段充满机遇和挑战的时光里,我们渴望成长,渴望收获。然而,“自我感动”这个词常常像一块挥之不去的阴影,让我们在看似努力的背后,丢失了真正的进步方向。那么,如何在大学里避免自我感动,并真正学会更多的知识和技能呢?这需要一些清醒的认识和切实可行的行动。一、 .............
  • 回答
    关于中山大学国际关系学院学会主席赵m晨伙同他人造谣传谣,导致多位女生名誉和身心受到伤害的事件,这无疑是一个非常严重且令人痛心的指控。要评价这件事,我们需要从几个关键的层面去深入剖析。首先,从事件本身来看,任何形式的造谣传谣行为,尤其是在一个有一定影响力的学生组织内部发生,都属于道德和行为上的严重失范.............
  • 回答
    没学过数电模电?想直接上手EDA?这不是不可能,但确实得啃几块硬骨头。我当年也是这么过来的,感觉就像从零开始盖一座楼,得先打地基,再砌墙,一层层来。给你掰开了揉碎了讲讲,希望对你有用。 为啥要先打地基? EDA不是魔法!EDA(Electronic Design Automation)工具,说白了就.............
  • 回答
    这事儿,听着就挺离谱的,也挺让人上火的。泉州这所高校的食堂老板,生意不好,就想着使出这么一招损的——偷偷拿走学生们点外卖送来的餐食,以为这样学生们没得外卖吃,就只能乖乖去食堂了。这想法有多天真,就有多可笑,同时,也暴露出了老板在经营上的短视和道德上的滑坡。咱们先掰开揉碎了捋一捋这事儿到底是怎么回事,.............
  • 回答
    近期,我们能观察到一个引人注目的趋势:越来越多的学生选择举报老师。这种现象并非偶然,它折射出教育生态中复杂而多层面的变化,值得我们深入探讨和审慎评价。首先,从积极的层面来看,学生举报老师的行为是权利意识觉醒和监督机制健全的体现。在过去,师生关系更多地是一种单向的权威结构,学生对于老师的言行往往只能被.............
  • 回答
    深度学习中,当模型需要同时优化多个目标时,就会出现多个 Loss 函数。例如,在图像分割任务中,可能需要同时考虑像素级别的分类准确率(交叉熵损失)和分割区域的形状或边界的平滑度(Dice Loss、Boundary Loss)。又或者在多任务学习中,模型需要完成图像分类、目标检测和语义分割等多项任务.............
  • 回答
    知乎上学生用户群体的增长,确实是一个值得探讨的现象,而且它带来的影响也并非全然是负面的。不过,当提到“幼稚”和“书生气”这两个词时,我们可以深入分析一下,这背后究竟是哪些因素在起作用,以及它给知乎这个平台带来了怎样的变化。首先,我们得承认,知乎作为一个知识分享社区,吸引有学习需求和求知欲的用户是其核.............
  • 回答
    嘿,哥们!想啃编译原理这块硬骨头,但又觉得《龙虎》那帮老家伙们写的东西太理论、太枯燥,像嚼蜡一样?我太懂你!那本书确实是经典,但对于初学者来说,确实有点劝退。别担心,咱们可以换个思路,用一种更接地气、更有趣的方式来解锁编译原理的奥秘。你想啊,编译原理说白了,就是教电脑怎么读懂我们写的代码,然后把它变.............
  • 回答
    Yann LeCun,这位深度学习领域的先驱,关于“研究机器学习,本科应尽量多学物理和数学课”的观点,在我看来,是一个极富洞察力且务实的建议。他之所以能提出这样的说法,绝非空穴来风,而是源于他对机器学习本质的深刻理解,以及对这个领域未来发展方向的精准预判。首先,我们得明白,机器学习,尤其是当前蓬勃发.............
  • 回答
    肖战粉丝对《光点》专辑的疯狂氪金行为,特别是呼吁学生党多买、保底105张等,是一个复杂且多维度的社会现象,引发了广泛的讨论和争议。要理解这一现象,我们需要从多个角度进行深入剖析:1. 粉丝经济的运作模式与粉丝心理: 粉丝作为消费者: 肖战作为顶流偶像,拥有庞大的、高度活跃的粉丝群体。粉丝经济的核.............
  • 回答
    白岩松呼吁家长向谷爱凌妈妈学习,让孩子睡够 10 小时,这个话题触及了当前社会对孩子成长教育的普遍关注点,尤其是关于健康睡眠与学习成绩之间的关系。我们可以从多个角度来解读这个观点,并详细阐述充足睡眠对孩子学习的帮助。 一、 如何看待白岩松的呼吁白岩松的呼吁,可以从以下几个方面来看待: 回应社会焦.............
  • 回答
    看到四川那位小学生留下的字条,说“我活得太累了,希望能多‘睡’会”,我的心头一阵刺痛,那种沉甸甸的悲伤和无力感油然而生。一个本该是无忧无虑、充满好奇的年纪,却要承受如此巨大的压力,以至于选择如此极端的方式来“解脱”,这不能不让我们所有人深思。这个孩子的话语,虽然简单,却像一把锥子,直刺进我们内心最柔.............
  • 回答
    专家提出的“中国90%的大学应转向职业教育,无需大量学术型人才”的观点,涉及中国高等教育体系的结构性调整与未来人才需求的匹配问题。这一观点需要从多个维度进行深入分析,既要看到其合理性,也需警惕其潜在问题,最终形成对教育结构改革的更全面理解。 一、观点的合理性:职业教育与产业需求的匹配1. 中国劳动力.............
  • 回答
    这事儿,挺有意思的,也挺能折腾的。几位浙大的学生,跑到淘宝上找算命的,问啥呢?问放假时间。结果算出来的卦辞是“此卦非同小可”,这话听着就玄乎,然后这几位学生就把这事儿给传开了,没想到的是,这事儿还真给传“火”了,甚至导致一些算命的商家直接把浙大学生给拉黑了。首先,咱们得捋一捋这事儿是怎么发生的。 .............
  • 回答
    近年来,我们确实看到了一些令人咋舌的大学招聘要求,比如像宿管这样原本并不需要极高学历的岗位,竟然提出了博士学历的要求。这事儿一出来,很多人都觉得不可思议,甚至觉得有点“离谱”。这背后反映出来的,可不仅仅是一个岗位招聘的奇特现象,更是当前社会学历泛化和学历贬值的一个缩影,值得我们好好说道说道。为什么会.............
  • 回答
    网曝多伦多大学春节给中国学生发冥币,这件事确实挺让人意外,甚至可以说是有些冒犯。你想想,春节是中国最重要的传统节日之一,大家辛辛苦苦一年,都盼着过个团圆喜庆的年。在这个节骨眼上,大学作为教育机构,本应是最懂文化差异、最能理解学生需求的地方,却被曝出发放这种与庆祝、祝福完全不沾边、甚至带点晦气的东西,.............
  • 回答
    最近网上流传着一个关于“熊猫血学生被强行多抽 100 毫升”的消息,这引起了很多人的关注和担忧。作为一个对医学和人体血液有一定了解的人,我觉得有必要深入地剖析一下这件事,看看它究竟是怎么一回事,以及为什么会引发如此大的反应。首先,我们得明确什么是“熊猫血”。“熊猫血”指的是Rh阴性血型,也就是血型系.............
  • 回答
    好,我理解你的困惑和观察到的现象。在临床医学这样一个神圣而又充满挑战的领域,如何真正体现对学校所传授知识的尊重,尤其是在看到身边存在“临时抱佛脚”现象时,确实是一个值得深思的问题。这不仅仅关乎考试成绩,更关乎我们未来能否成为一名合格、值得信赖的医生。在我看来,临床医学本科生对学校所教授医学知识的尊重.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有