Python技术迷

好多央国企,工资到手才四五千,里面一大堆都是硕士研究生学历。工资明明这么低,为啥央国企还能把这帮高学历的年轻人稳稳留住?

央国企到手四五千,听着确实不像硕士该拿的钱。可问题是,很多人进去看的根本不只是那几千块工资条。它稳,节奏没那么吓人,社保公积金规规矩矩,家里人一听单位名字也放心。

而且年轻人现在也不傻,外面民企看着月薪高,动不动加班到半夜,项目一黄部门说没就没。你说多拿三五千,换天天心里打鼓,真不一定划算。

Image

还有一点挺现实,央国企这个履历,对不少人来说就是个“安全垫”。先进去占个坑,考证、考编、等机会,哪怕跳槽也不难看。

所以不是大家都爱低工资,是很多人被社会毒打过之后,开始知道稳定这玩意儿,确实也值钱。工资低是真的,离不开也是真的。挺拧巴,但很现实。

算法题:睡眠函数

time.sleep(2) 一写,接口卡住了。

不是慢,是整个线程被按在原地等。刷题里看到“实现一个睡眠函数”,很多人第一反应就是这个,但这题真要考的,往往不是“会不会暂停”,而是你知不知道该让谁暂停。

题目大概是这样:给一个毫秒数 millis,写一个函数,等待这么久之后再继续往下执行。

最粗暴的写法:

import time

defsleep(millis: int):
    time.sleep(millis / 1000)

这段能跑,没毛病。

但我一般不太喜欢在异步代码里看到它。因为它是阻塞式睡眠,线程真停在那里,啥也不干。你要是在爬虫、接口聚合、定时调度里这么写,日志大概率会长这样:

[worker-3] start task=sync_user
[worker-3] sleep 2000ms
[worker-3] next task waiting...

看着像等 2 秒,其实后面的活也一起被拖住了。

更像算法题答案的写法,应该是异步睡眠:

import asyncio

asyncdefsleep(millis: int):
await asyncio.sleep(millis / 1000)

用的时候:

import asyncio
import time

asyncdefmain():
    start = time.perf_counter()

await sleep(1500)

    cost = (time.perf_counter() - start) * 1000
    print(f"实际等待:{cost:.0f}ms")

asyncio.run(main())

这段代码的关键不是 1500 / 1000,这个谁都会。关键是 await 把当前协程挂起了,但事件循环没停。别的任务还能继续跑。

我当时排这种问题,最烦看到一堆异步函数里混进 time.sleep。表面上代码没报错,压测一上来,全是慢请求。

比如这样就很别扭:

asyncdefpull_order(order_id: str):
    print("开始拉订单", order_id)
    time.sleep(2)
    print("拉订单结束", order_id)

看函数签名是 async,里面却干了同步阻塞的事。这个坑比直接写同步代码还隐蔽。

换成异步版本:

asyncdefpull_order(order_id: str):
    print("开始拉订单", order_id)
await asyncio.sleep(2)
    print("拉订单结束", order_id)

再看并发效果:

import asyncio
import time

asyncdefpull_order(order_id: str):
    print("start", order_id)
await asyncio.sleep(2)
    print("done ", order_id)

asyncdefmain():
    start = time.perf_counter()

await asyncio.gather(
        pull_order("A1001"),
        pull_order("A1002"),
        pull_order("A1003"),
    )

    print("总耗时:", round(time.perf_counter() - start, 2))

asyncio.run(main())

正常结果差不多是 2 秒,不是 6 秒。

这才是睡眠函数在 Python 里的重点:同步场景用 time.sleep,异步场景用 asyncio.sleep。别看名字都叫 sleep,影响范围完全不一样。

这题真要写得扎实一点,还可以自己封一层毫秒版:

asyncdefsleep_ms(millis: int):
if millis < 0:
raise ValueError("millis 不能是负数")

await asyncio.sleep(millis / 1000)

多这一个判断,不是为了显得代码完整。线上参数从来不保证干净,尤其是这种延迟、超时、重试间隔,传个负数、空值、字符串都不稀奇。

睡眠函数本身没难度,难的是别在错误的地方让整个系统一起睡过去。