宋宝华: 论一个程序员问问题的自我修养(修订版)

640?wx_fmt=png&wxfrom=5&wx_lazy=1

问问题如打麻将node


麻场如战场,麻品如人品,检验一我的的最好方法就是带他去搓麻将,一叶而知秋。一样地,经过程序员问问题的情状也基本能够看出一个程序员的治学态度。若是他问问题的时候,看得出他通过深思熟虑,通过本身的debug和剖析,这个程序员多半仍是一个不错的程序员,或者正在走向“不错”。反之,一眼看出,其问的问题就没有通过大脑,甚至连问题的现场都描述不清楚,这样的程序员多半在编程领域难有大的建树,极可能只是在终年累月地低水平重复建设。git

0?wx_fmt=png

一个好的程序员,不断经过解决工程问题,查阅资料,总结知识,提高认知来逐步把本身变成高手。一个很差的程序员,有点什么事情就开始处处问,也历来不深刻地去本身研究,其实,就算是工做20年,水平恐怕也仍是不行,由于他陷入了简单的经验式重复。高手不彻底是知识面广或深,很大程度上高在其自己的认知层面,而认知层面的提升就在于他历来都是在思考。程序员

 

我是一个码农,也号称是一老湿,接触的码农比较多,生平阅码农无数(哈哈,吹牛的,阅人莫道千千万,识得真心在最初)。 好的程序员,基本上,问问题的时候,给你春风拂面的感受,让你内心生出一些敬佩;很差的程序员,不少时候问的问题,你根本无言以对,内心一万个草泥马奔涌而过。github

0?wx_fmt=png

麻品有问题编程


下面咱们来总结一些很差的程序员问问题的时候的几个典型的“问题”。后端

0?wx_fmt=jpeg

不调试就问函数

以前有时候坐在office的时候,有些人问我这样的问题,“个人程序hang死了,请教一下hang死在哪里了?”我这个时候只想说,程序是你的,你都不知道哪里hang死了,我怎么就会知道?能不能拿gdb attach上去看啊?你拿gdb attach上去看,最后程序是拿不到mutex呢?仍是进while(1)了呢?仍是怎么的?工具

任什么时候候,问问题以前,先问本身一个问题,“我调试过没有?”没有的话,赶忙先闭嘴。oop

下面咱们用一个最简单的例子来解释这个过程,看下面一个程序,2个线程拿2个锁,可是顺序相反,程序确定会死锁。死锁以后,gdb attach上去看,不要问! 学习

pthread_mutex_tmutex_1;

pthread_mutex_tmutex_2;


void *child1(void*arg)

{

       while(1) {

              pthread_mutex_lock(&mutex_1);

              sleep(3);

              pthread_mutex_lock(&mutex_2);

              printf("thread 1 get running\n");

              pthread_mutex_unlock(&mutex_2);

              pthread_mutex_unlock(&mutex_1);

              sleep(5);

       }

}


void *child2(void*arg)

{

       while(1) {

              pthread_mutex_lock(&mutex_2);

              pthread_mutex_lock(&mutex_1);

              printf("thread 2 get running\n");

              pthread_mutex_unlock(&mutex_1);

              pthread_mutex_unlock(&mutex_2);

              sleep(5);

       }

}


int main(int argc,char *argv[])

{

       int tid1,tid2;


       pthread_mutex_init(&mutex_1,NULL);

       pthread_mutex_init(&mutex_2,NULL);

       pthread_create(&tid1,NULL,child1,NULL);

       pthread_create(&tid2,NULL,child2,NULL);

       ...

}

上述程序跑着必定会死锁,以后咱们gdb attach它的pid上去调试就能够分析出来它是怎么样在死锁。咱们首先看到它有3个线程:

(gdb) i threads

  Id  Target Id         Frame

  3   Thread 0xb75e9b40 (LWP 15809) "a.out" ...

  2   Thread 0xb7deab40 (LWP 15808) "a.out"...

* 1    Thread 0xb7deb700 (LWP 15804)"a.out"...

切到线程2,看一下backtrace:

(gdb) thread 2

[Switching to thread 2 (Thread 0xb7deab40 (LWP 15808))]

(gdb) bt

...

#4 0x08048627 in child1 (arg=0x0) at deadlock.c:12

...

这个PID为15808的线程在代码的第12行,child1()这个函数,等一个mutex,等谁呢?

(gdb) l child1

...

12                 pthread_mutex_lock(&mutex_2);

...

看出代码12行在等mutex_2,看下mutex_2的owner是谁?

(gdb) p mutex_2

$1 = {__data ={__lock = 2, __count = 0, __owner = 15809, __kind = 0, ...

咱们目前看出来,15808线程在等一个mutex,这个mutex目前被15809拿着。咱们如今切到线程3,而后看backtrace:

(gdb)thread 3

[Switchingto thread 3 (Thread 0xb75e9b40 (LWP 15809))]

(gdb) bt

...

#4  0x08048677 in child2 (arg=0x0) at deadlock.c:24

...

一样看出15809在等一个mutex,在代码的第24行。第24行在等谁呢?

(gdb) l child2

...

24                 pthread_mutex_lock(&mutex_1);

...

第24行在等mutex_1,它的owner又是谁呢?

(gdb) p mutex_1

$2 = {__data ={__lock = 2, __count = 0, __owner = 15808, __kind = 0...

因而可知,15808在等一个mutex,该mutex被15809拿着;15809在等一个mutex,该mutex被15808拿着。显然是死锁了。

上述例子很是简单,但足够说明咱们调试问题的方法和思惟问题的出发点。

不少时候,咱们依赖于backtrace(栈回溯),好比kernel的lockup、kernel的OOPS(哎呀!)、应用软件的hang死不动。


没有回溯崩溃或hang死现场的问问题都是耍流氓!

0?wx_fmt=jpeg

无背景就问

任何问题,都必有它的特定背景和再现流程,否则就反证了它是一个general或者common的问题,general的问题自己不是问题,不存在须要debug的问题,基本你经过无数渠道均可以得到答案。


因此,任什么时候候,问问题,应该描述清楚你全部的平台背景,以及你是如何再现的,缺少足够信息的问题,就和缺少足够信息的报bug是一回事。好比,做为一个测试工程师,你报的bug是说手机会死机,可是没有说什么手机,什么安卓版本,哪天的release,再现的流程,这种bug的报法,属于无效bug,只会被RD直接ignore。


下面来看一段真实的对话,它突显了我霸气和焦躁的不良品质 :-)

0?wx_fmt=png


0?wx_fmt=png


问题状况都说不清楚的状况下,你问问题,显然就是讨骂。好比你说RAMDISK在板子上面跑CRC错误,那么你先有没有在本身电脑上面解开过,好比执行mount -o loop或者cpio提取?


若是你电脑上面本身都没有试过,都不肯定RAMDISK是否是OK的,问板子还有什么意义?另外,问板子的问题,就应该描述清楚本身板子的内存大小,RAMDISK大小,存放的内存为位置,RAMDISK读出的介质如NAND/SD驱动有无问题等一系列状况,首先排除一些最基本的可能性。

骂完了我心理也有点难受了,感受本身有点过了。可是个人好基友说,好老湿和好医生是同样的,医者父母心。上来就骂同窗,确定是招人恨啊,可是一生让你不修正本身的思惟方式,那么岂不是更加地不负责任?


没有背景信息和再现流程的问问题都是耍流氓!

0?wx_fmt=jpeg

不学习就问

不少问题,你其实稍微学习一下就没有必要问了。好比,“请问,一个Linux普通进程多久被调度到?”这样的问题原本就是很吐血,认真学习后,不会问这样的问题。明确地说,不知道!装逼的说法,就是“depend on …”,依赖的东西太多。再装逼的说法,就是“一言难尽”,但这也是大实话。

普通的Linux进程为人比较善良,咱们通常用nice来形容它们的优先级,nice越高,优先级越低(你越nice,就越喜欢在地铁让座,固然越坐不到座位)。普通进程的跑法,并非nice低的必定堵着nice高的(要否则还说什么“善良”),它是按照以下公式进行:

vruntime=  pruntime*NICE_0_LOAD / weight

其中NICE_0_LOAD是1024,也就是NICE是0的进程的weight。vruntime是进程的虚拟运行时间,pruntime是物理运行时间,weight是权重,权重彻底由nice决定,以下表:

0?wx_fmt=png

在RT进程都睡过去以后(有一个特例就是RT没睡也会跑普通进程,那就是RT加起来跑地实在过久过久,普通进程必须喝点汤了),Linux开始跑NORMAL的,它倾向于调度vruntime(虚拟运行时间)最小的普通进程,根据咱们小学数学知识,vruntime要小,要么分子小(喜欢睡,I/O型进程,pruntime不容易长大),要么分母大(nice值低,优先级高,权重大)。这样一个简单的公式,就同时照顾了普通进程的优先级和CPU/IO消耗状况。

好比有4个普通进程,以下表,目前显然T1的vruntime最小(这是它喜欢睡的结果),而后T1被调度到。


pruntime

Weight

vruntime

T1

8

1024(nice=0)

10*1024/1024=8

T2

10

526(nice=3)

10*1024/526 =19

T3

20

1024(nice=0)

20*1024/1024=20

T4

20

820(nice=1)

20*1024/820=24

而后,咱们假设T1被调度再执行12个pruntime,它的vruntime将增大delta*1024/weight这里delta是12,weight是1024),因而T1的vruntime成为20,那么这个时候vruntime最小的反而是T2(为19),此后,Linux将倾向于调度T2(尽管T2的nice值大于T1,优先级低于T1,可是它的vruntime如今只有19)。

因此,普通进程的调度,是一个综合考虑你喜欢干活仍是喜欢睡和你的nice值是多少的结果。鉴于此,咱们去问一个普通进程的调度延迟究竟有多大,这个问题,自己意义就不是特别大,它彻底取决于当前的系统里面还有谁在跑,取决于你唤醒的进程的nice和它前面喜欢不喜欢睡觉。

相似的知识性问题还有不少不少,这些问题,彻底能够经过提早学习来解答。没有必要非得问。


知识性的问题,多半也很容易在网上找到资料。好比,搞不清楚PHP、node.js和Python作后端谁好,随便一百度,知乎网友回答的一大堆。若是再去问google,那会拿到更多的答案。“敏而好学,不耻下问”当然重要,但“敏而好学,耻于上问”也一样重要。


没有通过基本的学习就问问题都是耍流氓!


0?wx_fmt=png

作一个好麻友


个人带头大哥,如今跟好朋友打麻将,都是直接支付宝付款,每局谁胡牌后,支付宝直接转帐,麻品那是至关地好。而后打2个小时的牌,产生了几千上万的GDP,为国家和人民作了很大的贡献。


下面建议Linux程序员创建以下好习惯,作一个愿赌服输的好男人:

0?wx_fmt=jpeg

有问题,找男人

有问题,先man。“请问fread的第二个参数怎么设置?”鬼晓得?你man啊!

$ man fread

0?wx_fmt=png

我man过了,我man open的时候出来的竟然不是open(),而是openvt:

0?wx_fmt=png

因而就要

$ man 2 open

这个时候出来的才是open,请问这个2是个什么鬼?那么,能够man一下man。

$ man man

因此,有问题,找男人,男人若是有问题,继续找男人

不知道找哪一个男人,只记得函数里面有个单词叫timer。那么先定位,能够用相似apropos的工具:

baohua@baohua-VirtualBox:~/develop$ apropos timer

getitimer (2)        - get or set value of an interval timer

IPC::Run::Timer (3pm) - - Timer channels for IPC::Run.

setitimer (2)        - get or set value of an interval timer

time (7)             - overview of time and timers

timer_create (2)     - create a POSIX per-process timer

timer_delete (2)     - delete a POSIX per-process timer

...

再man那个函数便可。

提示


欢迎单位的女程序员有问题后直接请教男同事,

美女程序员直接忽略本文的全部条款,

美女程序员不受任何问问题规则约束  :-)


0?wx_fmt=jpeg


有问题,找Google

有的程序员又跳出来了,“报告,我google上不去”,相信我,一个不会翻墙的程序员,多半不会是一个好程序员。你连墙都不会翻(或者懒得翻的可能性更大),又如何查阅到最合适你的资料?

“报告,翻墙要花钱”,那么,吃饭要不要花钱?对于程序员来讲,翻墙的需求,基本和吃饭的需求是同样的。翻墙越多,水平越高(翻墙看YY网站除外,那无非是多认识几个老师)。


咱们不是嘲笑baidu必定不行,而是不少时候,工做的时候,碰到的kernel panic,碰到的segmentation fault,不少的其余的现场,它都是以英文的形式来呈现,极可能你在google里面能直接搜索到别人跟你碰到一样的问题。抛开baidu卖狗皮膏药的道德层面问题不谈,baidu在英文方面的搜索能力显然是远远落后google的。


下面咱们搜索Kernel hard lockup,百度显示以下(应该说还不错):

0?wx_fmt=png

Google显示以下(精准地定位到了Linux kernel官网的文档):

0?wx_fmt=png

咱们不是崇洋媚外,咱们只是正视血淋淋的现实。

0?wx_fmt=jpeg

感恩回答者

不少程序员问别人问题,以为别人天经地义应该回答你。并且问了很stupid的问题,别人没有好好回答,还以为回答者在装逼。其实反过来想,必定要明白,回答咱们问题的人,其实真的是在给咱们工做。几年前,我在LKML问Greg K.H.下一个LTSI(工业级稳定版Linux) 版本是什么,他给个人回答很是简单直接,体现了一个正确的回答问题的风范:


0?wx_fmt=png

“you are asking me to do work for you, right?”,你问别人问题的时候,你在让别人为你工做(并且这个工做仍是免费的),对不对?

 

最好的问问题的方法,就是尽可能不要问问题。若是要问题,要问能打动被提问者的问题。如何打动?你问问题,显示你已经通过足够的思辨。建议每个人,能够深度学习一下《How-To-Ask-Questions-The-Smart-Way》这个文档,它是一个git项目哦:

https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way


0?wx_fmt=png

最后的话

他回忆说:“个人名字是妈妈取的,我问过她为何给我取这么奇怪的名字,她说是王勃的《滕王阁序》'雄州雾列,俊采星驰,台隍枕夷夏之交,宾主尽东南之美'这句来的。”


哪怕是一个龙套,咱们也要认真地去作。否则就会永远是个龙套。由于,这是一个演员的自我修养。

0?wx_fmt=jpeg

不会问问题(意味着历来不试图本身去解决问题),咱们恐怕永远是一个龙套。当你碰到一个bug,你去问别人,而后别人帮你快速解决了,本身的所学是有限的。而本身陷入思考,真正去debug,去查阅资料,去绞尽脑汁,这个过程的得到,除了这个修复bug自己之外,更可能学到了不少的知识,获取了不少的经验,提高了本身的认知。而这种认知,是你问一万个问题,而且当即得到了答案,也没法取得的。


小贴士


三个耍流氓

0?wx_fmt=png


0?wx_fmt=png

不调试就问

无背景就问

不学习就问



三个好习惯

0?wx_fmt=png


0?wx_fmt=png

有问题,找男人

有问题,找Google

感恩回答者



0?wx_fmt=png

苹果手机打赏

0?wx_fmt=png


0?wx_fmt=gif


0?wx_fmt=png安卓手机打赏0?wx_fmt=png