Python技术迷

渐渐能理解为何不愿意雇佣35岁以上程序猿。去年换了份工作,组里4位组员其中3位40+,发现其实最大的问题并不是说精力不济卷不动

刚看到个贴子,说博主去年跳槽,新组里4个人有3个40+程序员,他说这段时间渐渐理解为啥很多公司不愿意招35+。
网友回帖有骂老板嫌老爱用廉价年轻人的,也有替中年程序员抱不平,说人家经验宝贵。 我觉得这事吧,关键真不在“精力不行卷不动”,而是心态和更新速度。有的前辈业务很熟,但抗拒新技术,开会一言不合就摆资历,当自己是“顾问”,不太愿意背锅干脏活累活;遇到变动,第一反应是抱怨,而不是想怎么升级自己。 换个角度想,公司算的是性价比:给到这个年薪,你是不是还在持续产出、肯学肯变通、能带团队。 说到底,年龄只是放大镜,放大的是之前几年你对自己的投资。与其怕35+被淘汰,不如从现在开始,让自己哪怕40岁,依然是那个“最难被替代的人”。

算法题: H2O 生成

昨天晚上十一点多,我正准备关电脑睡觉,我们组那个小李突然给我丢了个截图过来,说:“东哥,这个 H2O 生成到底咋写啊,用 python 的那个多线程版本,我脑子已经浆糊了”。我一看,嗨,这不是面试高频题嘛,干脆就顺嘴跟你们也唠一遍。 用大白话说一下哈,不照着题面背。
  • 你有一堆线程,有的负责“打 H”,有的负责“打 O”
  • 每个氢原子线程会调用 hydrogen() 方法,方法里会执行一个 releaseHydrogen() ,你可以理解成打印 "H"
  • 每个氧原子线程会调用 oxygen() 方法,里面执行 releaseOxygen() ,理解成打印 "O"
  • 要求是: 输出要按水分子来凑 ,也就是每 3 个字符必须刚好是 2 个 "H" 和 1 个 "O" ,顺序不限,比如 HHO 、 HOH 都行,但不能出现 HHH 或者 OOOO 这种乱的
重点是:这些线程是 并发 跑的,不是你一个 for 循环按顺序 print,那就简单了。所以整个题目其实是在考: 多线程同步 + 资源配额控制 。 你可以脑补一个场景:门口有个保安,只允许 2 个氢 + 1 个氧一起进实验室配成一杯水,多一个少一个都不行。 最常见的错误思路就是:搞两个全局计数器:
h_count = 0
o_count = 0
然后:
def hydrogen(...):
    global h_count
    h_count += 1
    releaseHydrogen()
    # 当 h_count == 2 and o_count == 1 的时候,说明可以组成一杯水
看着好像有点道理对吧?但问题一大堆:
  • h_count += 1 这种操作在多线程下本身就不是原子的,会冲突
  • 即便你加了锁,还得想办法“卡住”多余的 H 或 O,不让他们乱印
  • 还要保证老的一杯水凑齐了,再开始下一杯,不然很容易变成“2 杯水掺成一盆水”
所以这种“靠计数自己算”的写法, 越补越乱 。面试的时候一般写着写着你自己都迷糊了。 我一般跟新人说,就把这题当“发票”的题看就行:
  • 氢线程 要进来打字,必须先拿到一张“氢票”
  • 氧线程 要进来打字,必须拿到一张“氧票”
  • 每次配水的流程是:允许 2 张氢票进来 + 1 张氧票进来,三个人都干完活,再发下一波票
在 Python 里特别适合干这个事的东西就是 Semaphore (信号量)。 我们来直接看一版代码,然后再拆开聊,每行都别紧张,看几遍就顺了。
import threading
from typing import Callable


class H2O:
    def __init__(self):
        # 一开始允许 2 个 H 先来
        self.h_sem = threading.Semaphore(2)
        # O 一开始不能来,等 H 凑够了再放行
        self.o_sem = threading.Semaphore(0)

        # 记录当前这一杯水已经来了几个 H
        self.h_count = 0
        self.lock = threading.Lock()

    def hydrogen(self, releaseHydrogen: Callable[[], None]) -> None:
        # 先抢一张 H 的“门票”
        self.h_sem.acquire()

        # 真正打印 H
        releaseHydrogen()

        # 更新“这一杯水已经来了几个 H”这个状态
        with self.lock:
            self.h_count += 1
            # 如果已经有 2 个 H 了,就可以放行 1 个 O 进来了
            if self.h_count == 2:
                # 允许一个 O 线程通过
                self.o_sem.release()

    def oxygen(self, releaseOxygen: Callable[[], None]) -> None:
        # 没有人放行之前,O 是不能随便来的
        self.o_sem.acquire()

        # 真正打印 O
        releaseOxygen()

        # 这一杯水完工了,状态重置,为下一杯水发票
        with self.lock:
            # 当前这杯水的 H 用完了
            self.h_count = 0
            # 发 2 张新 H 票,供下一杯水使用
            self.h_sem.release()
            self.h_sem.release()
你可以在脑子里模拟一轮:
  1. 刚开始 h_sem 里有 2 张票,所以最多两个 H 线程能通过 acquire ,第三个 H 会在这里堵着
  2. 前两个 H 执行完 releaseHydrogen() 之后,会把 h_count 加到 2
  3. 第二个 H 把 h_count 改到 2 的时候,会 self.o_sem.release() ,等于发了一张 O 的票
  4. 某个 O 线程拿到这张票, acquire 成功,然后打印 O
  5. O 打完之后,重置 h_count = 0 ,再放出 2 张 H 票,下一杯水又可以开始凑了
这样就保证了:
  • 每次 至少 要有 2 个 H + 1 个 O 才能一起完成一个循环
  • H 多了会在 h_sem.acquire() 这里排队,O 多了会在 o_sem.acquire() 这里排队
  • 任何时候,你往输出上面看,一定是按 3 个字符一组,每组都刚好“2H + 1O”

为啥还要那把 lock?

有人看到这里会问一句:“既然 h_sem 控制 H 的数量了,那 h_count 还要不要加锁啊?” 要的。 原因是: self.h_count += 1 不是原子操作,两个 H 线程几乎同时修改这个值,会导致:
  • 一个线程读到旧值
  • 两个线程分别写回去
  • 最后结果变成 1,而不是你以为的 2
所以我们用 with self.lock: 把这个“读 + 改 + 判”的一套动作包成一个不可拆的小块,保证一次只有一个线程在里面操作。 在多线程的世界里,一个很重要的习惯就是: 只要有共享状态,就先想锁,再想逻辑 ,不然 bug 会非常阴间,而且不好复现。 你要是想本地跑一跑,可以自己写个小 main,随手贴一个简化版的测试:
import threading
import random
import time


def test():
    h2o = H2O()

    def printH():
        print("H", end="")

    def printO():
        print("O", end="")

    threads = []

    # 随便造一点 H、O 线程,数量要满足 2:1
    for _ in range(10):
        t = threading.Thread(target=h2o.hydrogen, args=(printH,))
        threads.append(t)
    for _ in range(5):
        t = threading.Thread(target=h2o.oxygen, args=(printO,))
        threads.append(t)

    # 打乱顺序,更接近真实场景
    random.shuffle(threads)

    for t in threads:
        t.start()
        # 故意加一点点随机延迟
        time.sleep(0.01)

    for t in threads:
        t.join()


if __name__ == "__main__":
    test()
你随便跑几次,可能会看到这样的输出:
  • HHOHHOHHOHHOHHO
  • 或者 HOHHOHHOHHOHHO
顺序乱点没关系,只要你拿三个一组数一下,都是 2 个 H + 1 个 O 就说明逻辑没问题。 -END -
我为大家打造了一份RPA教程, 完全免费: songshuhezi.com/rpa.html 🔥 虎哥私藏精品 🔥 虎哥作为一名老码农,整理了全网最全 《python高级架构师资料合集》 ,总量高达 650GB