熬过了五轮技术面,终于拿下京东40K的offer,却倒在了背调环节,京东的背调,从来不只是走个过场。
刚看到个贴子,说有网友熬过五轮技术面,好不容易拿到京东40K的offer,结果卡在背调,直接被撤。 网友们的回复我看了看,有骂京东背调太狠的,有怀疑是前东家乱说话的,也有人嘲讽“谁让你简历不老实”。
我觉得这事吧,关键还是“职业信用”。背调不是走过场,人家花那么多钱招你,肯定要把过去翻一翻:离职原因、绩效、有没有乱摊事,迟早都会露出来。你不能一边想拿大厂高薪,一边对过去一笔勾销。
不过话说回来,有些公司和同事在背调里补刀、添油加醋,这种确实恶心,事实就说事实,别拿大伙前途出气。
从我的角度看,打工人能做的,就是别造假、别编故事,该交代的提前想好,好聚好散。offer会飞,但把职业信用护住了,路没断,下一个机会总轮得到你。
算法题:按序打印
那天晚上快十一点,我在公司楼下蹲着喝奶茶,我们组那个小李突然冒一句:哥,Python 多线程那个“按序打印”你咋写的?我愣了一下,说你先等我喝一口,别一上来就拷打灵魂。
先说下这道题到底在干嘛
题目意思其实特别日常:有三个线程,分别负责打印:
第一个线程:打印 first第二个线程:打印 second第三个线程:打印 third
系统调度是乱序的,也就是说第三个线程很可能先跑、第二个后跑,完全不听你的。 但要求是:无论线程怎么启动、怎么抢占 CPU,输出必须是:
firstsecondthird
顺序不能乱。
你看着是不是很像现实里的场景:下单 → 支付 → 发货,这三步顺序不能乱,对吧。
先说一个“错得很有代表性”的写法
很多人一上来会这么整,伪代码大概是这样:
from threading import Thread
deffirst():
print("first", end='')
defsecond():
print("second", end='')
defthird():
print("third", end='')
t1 = Thread(target=first)
t2 = Thread(target=second)
t3 = Thread(target=third)
t1.start()
t2.start()
t3.start()
然后心里想:我明明先 start 了 t1,再 t2 再 t3,应该也会按顺序吧…… 结果一跑:secondfirstthird、thirdfirstsecond,啥都有,就是不给你老实听话。
这里有个坑:start() 只是“把线程扔给系统排队”,什么时候真正执行,不归你管,完全是操作系统调度器说了算,跟你代码里的顺序没啥关系。GIL 也救不了你,它只保证同一时刻只有一个 Python 字节码在执行,不保证线程之间的执行顺序。
正确思路:用“信号”把三个线程串起来
现实世界里面,靠什么保证顺序?红绿灯、排队号、通知短信,本质上都是:
前一个动作完成后,给后一个人发一个“可以开始了”的信号。
在 Python 里,我们可以用两种常见的小玩意儿当“信号”:
threading.Semaphore(信号量)threading.Event(事件)
这俩都是“一个线程等待,另一个线程通知”的模型,特别适合这种按序打印。
我先用信号量写一个完整的版本,一看就懂。
用 Semaphore 写一个经典解法
先看代码,类名就叫 Foo,跟题目里一样:
from threading import Semaphore, Thread
classFoo:
def__init__(self):
# second_sem 一开始是 0,表示 second 线程暂时不能过
# third_sem 也是 0,表示 third 线程也不能过
self.second_sem = Semaphore(0)
self.third_sem = Semaphore(0)
deffirst(self, printFirst):
# printFirst 是一个函数,内部帮你打印 "first"
printFirst()
# 告诉 second:我干完了,你可以开始了
self.second_sem.release()
defsecond(self, printSecond):
# 卡在这里等 first 释放信号
self.second_sem.acquire()
printSecond()
# 告诉 third:轮到你了
self.third_sem.release()
defthird(self, printThird):
# 卡在这里等 second 释放信号
self.third_sem.acquire()
printThird()
# 下面模拟三个线程乱启动
defprintFirst():
print("first", end='')
defprintSecond():
print("second", end='')
defprintThird():
print("third", end='')
foo = Foo()
t1 = Thread(target=foo.first, args=(printFirst,))
t2 = Thread(target=foo.second, args=(printSecond,))
t3 = Thread(target=foo.third, args=(printThird,))
# 故意乱序启动,看结果是不是还是 firstsecondthird
t3.start()
t2.start()
t1.start()
t1.join()
t2.join()
t3.join()
你可以脑补一下执行过程:
一开始 second_sem = 0,third_sem = 0不管 second、third线程多早启动,都会卡死在acquire()那一行,拿不到信号就不能继续只有当 first()执行完,调用second_sem.release(),second才能从acquire()那里醒过来second()打印完,放行third_sem最后 third()才能执行
所以输出顺序一定是 firstsecondthird,再怎么乱调度也乱不了。
这个写法的好处就是非常直观: 谁要等谁,就在谁前面卡一个 acquire(),前面的人干完活就 release() 一下。
换个写法:用 Event 也能搞定
如果你更喜欢“开关”风格的,可以用 threading.Event,一样简单:
from threading import Event, Thread
classFoo2:
def__init__(self):
self.first_done = Event()
self.second_done = Event()
deffirst(self, printFirst):
printFirst()
# 标记:第一步完成
self.first_done.set()
defsecond(self, printSecond):
# 等第一步完成
self.first_done.wait()
printSecond()
# 标记:第二步完成
self.second_done.set()
defthird(self, printThird):
# 等第二步完成
self.second_done.wait()
printThird()
Event 的语义就是:
wait():如果开关没打开,就在这里等set():把开关打开,所有在这里等的人都被放行
逻辑跟信号量版是一样的,只是写法更像“标志位”。
这个套路在哪些地方能用?
别觉得这题只是刷题网站上的玩具,工作里太多地方能套这个模型了,比如:
处理日志时一定要先写本地文件,再异步丢到 MQ,再标记数据库状态 订单流程里,创建订单 → 锁库存 → 发消息,这三步的顺序是刚性的 启动一个复杂服务时,必须等配置加载完、连接池建好,才能对外提供接口
这些场景的核心其实就是:
步骤之间有依赖关系,但执行主体可能是不同线程、不同协程,甚至不同服务。
那思路基本一样: 要么用“信号量/事件”把步骤串起来,要么用队列、MQ 之类的做更粗粒度的“按序”。
行了,按序打印就说到这,我先去给自己续一杯咖啡,有空你可以把上面的代码自己敲一遍,顺便试试故意把线程启动顺序打乱,看输出还能不能稳住。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB