Blog

toc:PersonalGrowth

精力 #

伊万·伦德尔的“战前准备程序” 伦德尔的良好习惯 先行后思 将时间和行为精准化和具体化 改变最好由浅入深 四条关键的精力管理法则 四种精力来源

GTD #

GTD概要 等待清单与下一步行动清单 达芬奇定期放下工作 以GTD来应对蔡格尼克效应 GTD方法的三个主要目的 帕金森定律与蔡格尼克效应

问题 #

问题就是理想状态和现实状态之间的差别 定义问题的两个要点

沟通 #

谈判时的共鸣 提出批评意见的三明治技巧

Misc #

腹部发声法 主持人如何处理别人的指责 创造力需要的两种能力 怀特海眼中的文明进步 你必须爱上失败 诱导性提问

阅读 #

这几本书其实是一本书

思辩 #

可证伪与不可证伪 思维的三重境界 预测使我们能够欺骗进化

反脆弱 #

什么是反脆弱

清单革命 #

复杂环境中专家需要应用的两大困难 简单问题、复杂问题和极端复杂的问题 两套清单应对极端复杂的问题 不能有一粒棕色巧克力豆 发现霍乱疫情的爆发源头 降低卡拉奇贫民窟儿童过高的夭折率 介绍自己的名字 精确、高效、切中要害 清单编制6大要点 操作确认和边读边做 新知识系统地转变为清单 投资家的清单

和这个世界讲讲道理 #

刺猬和狐狸 棘手问题(Wicked Problem) 应对棘手问题的建议 好歌想要流行需要很大的运气 把成长归结于吃苦是一种归因谬误 公平世界假设的三个害处 关系成功与否的三个影响因素 构建更重要 混杂信息(Muddled Information)与考试刷分 对指标的算法保密 坎贝尔定律(Campbell’s law) 最简单经济学的五个智慧 市场中过度保护某一群体是错误的 complex是比complicated更高级的复杂 任何大型社会项目的任何效果评估的预期值等于0 不断地变动自己的世界观 道德判断是直觉式的、感性的快速判断 曲木(crooked timber)传统 资本主义的本质是关于信息的 供给创造新的需求 三种面对市场的态度和三种教育境界 人类发展的两个制胜法宝 好社会的八大特征 趋同进化(convergent evolution) 进化不是完全随机的 政治格局中的三围 五个通用的权力规则 安理会效应 收集易拉罐的遗传算法 中国模式的巨大成功是因为起点低 先发表后过滤 红皇后假说(Red Queen Hypothesis) 四种事物的年龄和存活关系 七十年的坎 内卷就是向内演化(Involution) 中国高考的确是内卷 内卷与内耗 辉格史观 排位稀缺 三种排位稀缺 帝国的暴力只能来自边缘地带

chan导致的goroutine泄漏

Content #

goroutine 泄漏的问题也很常见,下面的代码也是一个实际项目中的例子:

func process(timeout time.Duration) bool {
    ch := make(chan bool)

    go func() {
        // 模拟处理耗时的业务
        time.Sleep((timeout + time.Second))
        ch <- true // block
        fmt.Println("exit goroutine")
    }()
    select {
    case result := <-ch:
        return result
    case <-time.After(timeout):
        return false
    }
}

在这个例子中,process 函数会启动一个 goroutine,去处理需要长时间处理的业务,处理完之后,会发送 true 到 chan 中,目的是通知其它等待的 goroutine,可以继续处理了。

我们来看一下select的用法,主 goroutine 接收到任务处理完成的通知,或者超时后就返回了。这段代码有问题吗?

如果发生超时,process 函数就返回了,这就会导致 unbuffered 的 chan 从来就没有被读取。我们知道,unbuffered chan 必须等 reader 和 writer 都准备好了才能交流,否则就会阻塞。超时导致未读,结果就是子 goroutine 就阻塞在"ch <- true"处永远结束不了,进而导致 goroutine 泄漏。

解决这个 Bug 的办法很简单,就是将 unbuffered chan 改成容量为 1 的 chan,这样"ch <- true"处就不会被阻塞了。

...

chan数据结构

Content #

chan 类型的数据结构如下图所示,它的数据类型是runtime.hchan。

下面我来具体解释各个字段的意义。

  1. qcount:代表 chan 中已经接收但还没被取走的元素的个数。内建函数 len 可以返回这个字段的值。

  2. dataqsiz:队列的大小。chan 使用一个循环队列来存放元素,循环队列很适合这种生产者 - 消费者的场景(我很好奇为什么这个字段省略 size 中的 e)。

  3. buf:存放元素的循环队列的 buffer。

  4. elemtype 和 elemsize:chan 中元素的类型和 size。因为 chan 一旦声明,它的元素类型是固定的,即普通类型或者指针类型,所以元素大小也是固定的。

  5. sendx:处理发送数据的指针在 buf 中的位置。一旦接收了新的数据,指针就会加上 elemsize,移向下一个位置。buf 的总大小是 elemsize 的整数倍,而且 buf 是一个循环列表。

  6. recvx:处理接收请求时的指针在 buf 中的位置。一旦取出数据,此指针会移动到下一个位置。

  7. recvq:chan 是多生产者多消费者的模式,如果消费者因为没有数据可读而被阻塞了,就会被加入到 recvq 队列中。

  8. sendq:如果生产者因为 buf 满了而阻塞,会被加入到 sendq 队列中。

Viewpoints #

From #

13 | Channel:另辟蹊径,解决并发问题

四条关键的精力管理法则

Content #

四条关键的精力管理法则分别是什么?

  1. 全情投入需要调动四种独立且相关联的精力源:体能,情感,思维和意志。
  2. 因为使用过度和使用不足都会削弱精力,必须不时更新精力以平衡消耗。要想保持生命的跃动,我们必须学习如何有节奏地消耗和更新精力。
  3. 为了提高能力,我们必须突破自己的惯常极限,模仿运动员进行系统训练。
  4. 积极的精力仪式习惯,即细致具体的精力管理方法,是全情投入、保持高效表现的诀窍。

From #

ArrayBlockingQueue中对条件变量的使用

Content #

条件变量(java.util.concurrent.Condition),如果说 ReentrantLock 是 synchronized 的替代选择,Condition 则是将 wait、notify、notifyAll 等操作转化为相应的对象,将复杂而晦涩的同步操作转变为直观可控的对象行为。

条件变量最为典型的应用场景就是标准类库中的 ArrayBlockingQueue 等。

我们参考下面的源码,首先,通过再入锁获取条件变量:

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;

public ArrayBlockingQueue(int capacity, boolean fair) {
  if (capacity <= 0)
      throw new IllegalArgumentException();
  this.items = new Object[capacity];
  lock = new ReentrantLock(fair);
  notEmpty = lock.newCondition();
  notFull =  lock.newCondition();
}

两个条件变量是从同一再入锁创建出来,然后使用在特定操作中,如下面的 take 方法,判断和等待条件满足:

public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  try {
      while (count == 0)
          notEmpty.await();
      return dequeue();
  } finally {
      lock.unlock();
  }
}

当队列为空时,试图 take 的线程的正确行为应该是等待入队发生,而不是直接返回,这是 BlockingQueue 的语义,使用条件 notEmpty 就可以优雅地实现这一逻辑。

...

keypress与keydown的区别

Content #

keypress事件与keydown事件的主要区别在哪里? keypress事件只能由可打印字符的按键来触发。

From #

小孩在学校摔伤的责任认定

Content #

小孩在学校课间爬桌子摔伤,法律上对学校的责任是如何认定的?

《侵权责任法》的第 38 条规定,在幼儿园、学校或者其他教育机构(以下统称为校方)学习、生活期间,如果无民事行为能力人受到伤害,校方应该承担责任;但是,如果校方能证明,自己已经尽到了教育和管理的职责,就不必承担责任了。这里实际用的是过错推定原则,只要发生事故,先推定学校要承担责任,然后,学校进行举证反驳。如果学校提供的证据可以证明,自己已经尽到了相应的义务,不存在过错,校方才能免责。

《侵权责任法》的第 39 条规定,在学校或者其他教育机构(以下统称为校方)学习、生活期间,如果限制民事行为能力人受到伤害,而校方并没有尽到教育、管理职责,校方应该承担责任。两条规定的差别显而易见。38 条首先推定校方要负责,除非校方可以举证证明自己无错。而 39 条则规定,校方有过错的话,才需要承担责任。那么,如果学校已经给学生讲过安全问题,尽到了管理职责,学生受伤后,学校是不用担责的。

如果小孩在8周岁以下,学校一般要承担责任。如果在8周岁以上,原则上,学校不需要承担责任。因为不能爬桌子上是一个最基本的常识,除非小五父母能证明学校有疏忽过错,学校才需要承担责任。

From #

on-call工程师的过度联想

Content #

在压力释放的荷尔蒙的影响下,on-call工程师往往会选择反应性的、未经详细考虑过的操作,这些操作很容易导致“过度联想”现象的产生。“过度联想”现象指的是什么?举例说明。

“过度联想”是在on-call中非常容易产生的现象,举例来说,当on-call工程师收到本周内第4个同样报警信息时,很容易联想起前3次报警都是由于某个外部系统造成的虚假报警,于是很自然地将第4次报警也归类为虚假报警,从而没有认真处理,导致真实事故的发生。

From #

公司控制权的争执

Content #

老许是一名技术很牛的自动化工程师,在业界也有一定知名度。去年年初,他应好友小吴的邀请一起下海创业。初创业时,两个人也没签什么书面协议,只是粗略分工,并口头约定了“一起打天下、日后平分”。后来公司业务发展越来越好,也算是慢慢做起来了。但在需要签协议时却出了问题,两个人关于股权的分配产生了严重分歧。悬而未决的股权问题,成了老许一大块儿心病。两人的公司主要是这么个情况:

  1. 公司最初的工商登记为小吴个人独资公司,老许加入后出资 30 万元,不过一直没签合同,也未做工商变更登记;

  2. 公司管理上,老许负责产品和技术,小吴负责运营和财务;

  3. 公司现有员工 30 多人,年净利润约为 200 万元。

两人存在的争议主要在于:老许认为自己既出资 30 万元,又有技术出资,因此应该拥有一半股权;小吴则认为公司是自已创建的,老许最多占到 30% 股权。老许自认为吃了亏,但是两人又争执不下,该怎么办呢?

显然,老许的状况还是比较棘手的。老许出了投资金,但因为跟小吴私交甚笃,并没有签股权转让协议或者是其他投资协议,更没有写进公司章程,进行工商登记。可以说,老许遇到的问题,是股权纠纷里是风险最大的一种。一旦小吴不承认他的股东身份,老许便是竹篮打水——一场空。至于给公司或者小吴的转账记录?那到底算是股东出资,还是私人借款,抑或是其他的生意往来账款,又如何能证明呢?幸好,老许还没有倒霉到遇上“白眼狼”,朋友小吴目前仍然承认他的股东身份。但是因为缺乏法律认定,主动权不在老许手里,所以,接下来的事情就要抓紧了。

  1. 老许一定要跟小吴友好地沟通,晓之以情,动之以理尽量争取最大的权益。

  2. 一旦达成一致意见,最重要的是迅速签好协议,修改公司章程,并尽快到工商部门办理工商变更登记。在利益协商上,哪怕稍稍吃点亏,老许也一定要尽快在法律上确认股东身份。

  3. 两人注意要约定好公司控制权以及股东退出机制的问题。

From #

如何实现日志的一致

Content #

在 Raft 算法中,领导者通过强制跟随者直接复制自己的日志项,处理不一致日志。也就是说,Raft 是通过以领导者的日志为准,来实现各节点日志的一致的。具体有 2 个步骤。

  1. 首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的了。

  2. 然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。

我带你详细地走一遍这个过程(为了方便演示,我们引入 2 个新变量)。

  1. PrevLogEntry:表示当前要复制的日志项,前面一条日志项的索引值。比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogEntry 值为 7。

  2. PrevLogTerm:表示当前要复制的日志项,前面一条日志项的任期编号,比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogTerm 值为 4。

标注在箭头上方。

  1. 领导者通过日志复制 RPC 消息,发送当前最新日志项到跟随者(为了演示方便,假设当前需要复制的日志项是最新的),这个消息的 PrevLogEntry 值为 7,PrevLogTerm 值为 4。

  2. 如果跟随者在它的日志中,找不到与 PrevLogEntry 值为 7、PrevLogTerm 值为 4 的日志项,也就是说它的日志和领导者的不一致了,那么跟随者就会拒绝接收新的日志项,并返回失败信息给领导者。

  3. 这时,领导者会递减要复制的日志项的索引值,并发送新的日志项到跟随者,这个消息的 PrevLogEntry 值为 6,PrevLogTerm 值为 3。

  4. 如果跟随者在它的日志中,找到了 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的日志项,那么日志复制 RPC 返回成功,这样一来,领导者就知道在 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的位置,跟随者的日志项与自己相同。

  5. 领导者通过日志复制 RPC,复制并更新覆盖该索引值之后的日志项(也就是不一致的日志项),最终实现了集群各节点日志的一致。

从上面步骤中你可以看到,领导者通过日志复制 RPC 一致性检查,找到跟随者节点上与自己相同日志项的最大索引值,然后复制并更新覆盖该索引值之后的日志项,实现了各节点日志的一致。需要你注意的是,跟随者中的不一致日志项会被领导者的日志覆盖,而且领导者从来不会覆盖或者删除自己的日志。

...