Python技术迷

工作中一个博士同事,代码写的一坨屎,需求不了解就直接开搞,埋了很多坑。明明做的效果不怎么样,但是领导还是很看重。无解了~

刚看到个贴子,说楼主公司有个博士同事,代码一塌糊涂,需求也不懂就乱写,坑一堆,效果一般,但因为学历高,领导还是格外看重,他就感叹:学历真是太重要了,普通人无解。

Image

网友回复我看了看,有人跟着骂领导只看学历不看能力,也有人说博士至少能拿项目、写汇报、撑门面,对领导有用,所以被护着也正常。

我觉得这事吧,先别急着上纲上线成“读书没用”或者“职场只看学历”。学历是入场券,但能不能站稳脚跟,还是看你能不能持续创造“对领导有用的价值”。很多领导图的是省心、可交代,有背景、有学历的人,在他们眼里风险更低,这就是现实。

不过话说回来,规则一时改不了,那就别只停在不爽上。把需求吃透、把活干干净、把沟通做到位,让别人一想到关键任务就自然点你名,这才是普通人能掌控的部分。

算法题:哲学家进餐

昨天晚上快十一点,我在公司楼下啃着冷掉的炸鸡,旁边几个同事在抢最后一杯奶茶,我一看这阵仗就笑了:这不就是“哲学家进餐”嘛,只不过哲学家换成了程序员。

你可以脑补这样一桌饭局: 圆桌,5 个程序员,每人面前一盘冒热气的火锅,还有一双筷子在他们和邻居中间“共用”。

规矩很奇怪: 想吃东西的时候,必须同时拿到左边和右边两只筷子; 没拿到就只能干坐着发呆(想问题)。

如果 5 个人同时都先拿起自己左边的筷子,然后一起伸手去拿右边的筷子,会发生啥? 每个人手里都有一只筷子,另一只都在别人手里,谁也不放,谁也拿不到第二只,全桌锁死——这就是并发里的“死锁”。

哲学家进餐这个题,就是让我们在“大家都想吃、筷子数量又不够”的情况下,想办法既不死锁,也不要有人永远吃不到(饿死)。

先别急着优化,先把最直接的版本写出来,感受一下问题在哪。下面这段代码用线程模拟 5 个哲学家,用 Lock 模拟 5 根筷子:

import threading
import time
import random

N = 5# 哲学家/筷子数量
forks = [threading.Lock() for _ in range(N)]

defphilosopher(idx):
    left = forks[idx]
    right = forks[(idx + 1) % N]
    name = f"哲学家-{idx}"

for _ in range(3):
# 思考
        print(f"{name} 在思考…")
        time.sleep(random.uniform(0.5, 1.0))

        print(f"{name} 想吃饭,先去拿左手边的筷子")
        left.acquire()
        print(f"{name} 拿到左筷子")

        time.sleep(0.1)  # 故意加一点延迟,更容易卡死

        print(f"{name} 再去拿右手边的筷子")
        right.acquire()
        print(f"{name} 拿到右筷子,开始吃!")
        time.sleep(random.uniform(0.5, 1.0))

        print(f"{name} 吃完了,放下筷子")
        right.release()
        left.release()

threads = []
for i in range(N):
    t = threading.Thread(target=philosopher, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

这个写法有一个共同点:每个人都先拿“左筷子”,再拿“右筷子”。 如果几个线程刚好节奏对齐了,就会出现 5 个人都拿着左筷子,谁也拿不到右筷子,一直卡在那里,程序就像假死一样不往下走。

解决思路特别生活化:既然大家一起上手容易卡死,那就让“最多只允许 4 个人同时去拿筷子”,总会有一个人被拦在外面,他两边的筷子里至少有一根是空着的,整个系统就不会全卡死。

这个“服务员”,在代码里面就可以用 Semaphore(信号量)来干:

import threading
import time
import random

N = 5
forks = [threading.Lock() for _ in range(N)]
# 最多只允许 4 个哲学家同时尝试吃饭
waiter = threading.Semaphore(N - 1)

defphilosopher(idx):
    left = forks[idx]
    right = forks[(idx + 1) % N]
    name = f"哲学家-{idx}"

for _ in range(3):
        print(f"{name} 在思考…")
        time.sleep(random.uniform(0.5, 1.0))

        print(f"{name} 想吃饭,先问问服务员还能不能上桌")
with waiter:  # 进入“吃饭区”,最多 N-1 人
            print(f"{name} 获准,可以去拿筷子了")

# 注意:这里可以固定一个顺序,降低出问题概率
            first, second = (left, right) if idx % 2 == 0else (right, left)

with first:
with second:
                    print(f"{name} 拿到两根筷子,开吃!")
                    time.sleep(random.uniform(0.5, 1.0))
                    print(f"{name} 吃完了,放下筷子")

threads = []
for i in range(N):
    t = threading.Thread(target=philosopher, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

这里有几个小点你可以顺手记一下(面试爱问):

  • 信号量 waiter = Semaphore(N-1) 的作用,就是保证“同时去抢筷子的人 < 筷子数”,不可能出现所有人都占着一根筷子等另一根的局面,所以不死锁。
  • 我顺手又加了一个小优化:偶数编号先拿左再拿右,奇数编号反过来,这样可以让竞争稍微分散一点点,也更不容易出现某个人老是抢不过别人的情况(避免“饥饿”)。

行了,先这样,我去泡个面。你可以把上面代码跑一跑,顺便把 print 换成 log,看一下线程交错的输出,很有画面感。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB