最近在社交媒体上看到一个热帖:楼主忽然发现身边不少研发同事的妻子都很漂亮,这些同事平时可能不善言辞,但在“找老婆”这件事上却都挺成功,这让楼主有些惊讶。

翻了翻评论区,观点大致分两种:一种是认为“程序员踏实顾家,适合结婚”;另一种则略带嘲讽地认为“还不是冲着钱去的”。我个人不太喜欢后一种说法,因为它不仅将女性简单归类为“拜金”,也把男性物化成了“钱包”。
从我的观察来看,许多理工科背景的研发同事确实具备一些优势:情绪相对稳定、不追求花哨、家庭责任感强,加上收入稳定,这些特质在婚恋市场中确实有竞争力。但这并不意味着“只要闷头干就行”。无论性格如何,沟通、共情和分担都是亲密关系中不可或缺的能力,长期缺乏这些,关系难免会疲惫。
换个角度想,与其单纯羡慕同事的“成功”,不如反思自身是否具备了可靠的人品、清晰的生活规划以及稳定的情绪管理能力。在云栈社区的开发者广场板块,也常能看到类似关于程序员生活与成长的讨论。
算法题:打印零与奇偶数
昨天晚上十一点多,下班等地铁的时候,我刷到一道看着简单、写起来却容易出错的题目——“打印零与奇偶数”。我一边等车一边在手机上画线程切换图,旁边的大爷看我写下一串“0 1 0 2 0 3”,还以为我在研究彩票号码。
题目是这样的:给定一个正整数 n,你需要启动三个线程:
- 线程 A:只打印数字
0。
- 线程 B:只打印奇数(1, 3, 5...)。
- 线程 C:只打印偶数(2, 4, 6...)。
但最终的输出顺序必须是:0 1 0 2 0 3 0 4 ... 0 n。
例如 n = 3,输出应为:0 1 0 2 0 3。线程可以并发执行,但打印顺序必须严格保证。这很考验对并发控制的理解。
一、先理清逻辑,别急着写代码
你可以想象有三个角色在按规则报数:
- A:只会喊“0”。
- B:只会喊奇数 1、3、5…
- C:只会喊偶数 2、4、6…
规则是:对于每一个从1到n的数字i,都必须先由A喊一个“0”,然后根据i的奇偶性,由B或C喊出i。
所以,对于每个i,顺序是锁死的:
- 若
i 是奇数: A (0) -> B (i)
- 若
i 是偶数: A (0) -> C (i)
程序如何实现这种“轮流发言”的规则呢?核心在于:让不该运行的线程等待,在恰当时机唤醒它。这就涉及到网络/系统层面常讨论的线程同步机制,比如信号量或锁。
二、用信号量串起三个线程
在Python中,使用 threading.Semaphore 来实现这个逻辑非常直观。我们可以设计三个信号量:
zero_sem:控制“打印0”的线程。
odd_sem:控制“打印奇数”的线程。
even_sem:控制“打印偶数”的线程。
初始状态时,应该先打印0,所以:
zero_sem 初始值设为 1(可立即获取)。
odd_sem 和 even_sem 初始值设为 0(被阻塞)。
然后制定严格的通行规则:
- 打印 0 的线程:
- 循环 n 次。
- 每次先获取
zero_sem,打印一个 0。
- 根据当前数字
i 的奇偶性,释放对应的 odd_sem 或 even_sem。
- 打印奇数的线程:
- 从1开始,每次递增2(1, 3, 5...)。
- 每次先等待获取
odd_sem,获取后打印当前奇数。
- 打印完成后,释放
zero_sem,让打印0的线程继续。
- 打印偶数的线程:
- 从2开始,每次递增2(2, 4, 6...)。
- 每次先等待获取
even_sem,获取后打印当前偶数。
- 打印完成后,释放
zero_sem。
整个流程就像一场精密的接力赛,核心节奏为:zero -> odd/even -> zero -> odd/even …,三个线程通过信号量互相传递“接力棒”。
三、完整的 Python 代码实现
下面给出完整的代码,你可以本地运行,验证输出顺序是否正确。
import threading
class ZeroEvenOdd:
def __init__(self, n: int):
self.n = n
# 一开始应该先打印 0,所以 zero_sem=1
self.zero_sem = threading.Semaphore(1)
# 奇数、偶数线程一开始都不能动
self.odd_sem = threading.Semaphore(0)
self.even_sem = threading.Semaphore(0)
def zero(self, printNumber) -> None:
"""
printNumber: 接受一个数字,把它打印出来的函数
"""
for i in range(1, self.n + 1):
# 等自己轮到(拿到 zero_sem)
self.zero_sem.acquire()
printNumber(0)
# 决定下一个是奇数线程还是偶数线程动
if i % 2 == 1:
self.odd_sem.release()
else:
self.even_sem.release()
def odd(self, printNumber) -> None:
for i in range(1, self.n + 1, 2):
# 等待被 zero() 放行
self.odd_sem.acquire()
printNumber(i)
# 打完自己的数字,再把机会还给 zero()
self.zero_sem.release()
def even(self, printNumber) -> None:
for i in range(2, self.n + 1, 2):
self.even_sem.acquire()
printNumber(i)
self.zero_sem.release()
# 简单跑个 demo
if __name__ == "__main__":
def printer(x):
# 为了看得清楚,加个空格不换行
print(x, end=" ")
n = 5
zeo = ZeroEvenOdd(n)
t_zero = threading.Thread(target=zeo.zero, args=(printer,))
t_odd = threading.Thread(target=zeo.odd, args=(printer,))
t_even = threading.Thread(target=zeo.even, args=(printer,))
t_zero.start()
t_odd.start()
t_even.start()
t_zero.join()
t_odd.join()
t_even.join()
# 预期输出:0 1 0 2 0 3 0 4 0 5
你可以尝试修改 n 的值(比如 1, 3, 10),观察输出是否始终符合 0 1 0 2 0 3 ... 0 n 的严格顺序。
这个解法利用了信号量,清晰地规定了线程间的执行依赖。如果你想挑战自己,可以尝试使用 threading.Condition 或 threading.Event 重新实现一遍,对比哪种同步原语更符合你的思维习惯。这类经典的线程同步问题是算法与数据结构学习与面试中很好的练兵场。