异步程序编写时候的一些小教训

不能不学习啊

Posted by 大狗 on December 15, 2020

异步程序编写时候的一些小教训

前言

写博客是为了什么?放松脑子,构建自己的模型

针对通俗事件的教训

  • 开会的时候,应当提前就确定好:1要讨论的点,2 自己的观点,3基本观点必须遵守的原则。将无用的思考/争论时间去掉。如果必然发生讨论,那么要注意基本原则的有效和一致性。
  • 代码的研发/BUG的报告要遵循一惯性的观点,不应当想到啥是啥。
  • 绕开难度大的东西,去做简单的事情是一种非常取巧的事情。这种追求简单的东西看上去似乎效率高,但实际上是一种懦弱的行为。正如同”你面前有两条路,选择荆棘丛生的那条”
  • 做事应该包含三个层面,本身层面:熟悉这东西的方方面面,横向对比和理念对比,纵向的观点
  • 判断事情是不是要做应当包含一个对边际效应的考虑,是不是值得做,有没有效益做需要考虑,但是这个效益要怎么考虑呢?
  • 思考问题的方法是自顶向下还是自底向上的呢?要避免手里是个锤子,看啥都是钉子

单独针对异步的教训

  • 任何内存申请,或者说明显的状态转换,乃至大的标记的转移,都需要在外部明显的标识出来,不要藏在犄角旮旯里。
  • 对于任何说出来的结论,都要进行论证,“不理智的人扭曲事实来适应理论,而不是改变理论来适应事实”,不要过早的得出结论,要找到足够的证据证明你的猜测。
  • 凡是参与到运算,或者说要多次采用的东西,都要保证其值不能在中间的时候发生改变,如果可能有其他线程修改,那就需要自己保存下来。
  • 如果类似内存申请的操作,需要好几个参与,其中一个失败了,剩下的几个务必也要释放。不要留着,否则重入可能造成内存leak

针对通用程序的教训

  • 程序在进行设计开发的时候应该进行良好的抽象与设计:模块化,层次化最好在一开始就保持。在进行后续代码的开发时,同样要求层次和模块的开发。层次的划分和模块的划分是重点与难点,建议看看《代码大全》
  • 对程序的测试应当经过充分的考虑,很多时候参数的有效性是否开放是要经过考虑的。

下面三条都是看《多处理器编程的艺术》时候的教训,代码逻辑&现实逻辑,具体的原有看第七章TAS & TTAS设计实现的感想:

  • 代码层面我们认为逻辑一致性的东西,已经在一定程度上避免了问题的出现,但是这种层面只是问题设计的第一层面
  • 因为代码是需要跑的,所以在实现的时候不但要考虑上层层面,还要考虑底层层面的实现,对锁而言就是缓存协议,同步等的代价
  • 最后一个就是要养成一个从上向下,看到底层的习惯了。还得尝试提出,实现方面的抽象。

代码整洁相关的教训直接看《代码整洁之道》里面总结的代码规范,

对代码的熟悉程度

什么叫做对代码的很熟悉?从我个人的角度来看,一般是以下几个方面:

  • 自动机中功能性的划分,能迅速找到功能划分的位置。
  • 针对某些异常情况的处理,能迅速找到在不同步骤出异常或问题时的处理流程。
  • 系统竞争和瓶颈所在地,能迅速找到可能导致并发 /新建问题出现的地方。
  • 多线程或多进程同步的地方,能迅速分析清楚具体是哪种同步模型。
  • 能够将模块中可拆分 /变化的部分迅速找到,并零件化
  • 附加一个学习thrift的时候的经验,应当熟悉并排查常见的运用该工具所遇见的错误和导致的问题,简单来说就是编程的时候需要遵守某种规范,那么如果不遵守这种规范,会出现什么错误和问题。

类比代码,可以得出对CI/CD环境的教训

写程序的时候要注意的

  • 写一个函数,要注意把每个覆盖的路径都正确,不能只保证一部分路径

  • 要注意层次,每层尽量只干一件事情,同时减少只出现一次的东西被单独拿出来初始化,比方说

    const std::string test_sets = request->test_sets();
      
    
  • if else不要出现太多条件,如果条件超过三个,建议成为函数

  • 分层,拆成一层一层的观点,逐步的减少复杂度

  • 接口,接口尽量明确,不要出现一个string打掉所有的情况,尽量明确到具体的比方说MAP<Key/value>

  • 对于一些新加的函数/接口/实现,不要追求和过去完全一致,可以改变数据结构的选择,使用map等,最直接的例子就是

    message ListScenariosRequest {
      optional string filter = 1;
      optional string page_token = 2;
      optional int32 page_size = 3;
      optional bool trace_history = 4;
      optional bool include_original_scenarios = 5;
      // test set filter
      optional string test_sets = 6;
      // experimental scenario test filter
      optional bool include_experimental = 7;
      // scenario name regex filter
      optional string scenario_re = 8;
      // china mode filter
      optional bool china_mode = 9;
      // match labels filter
      optional string match_labels = 10;    //这个地方就应该转换为map<string, string>来做,而不应该继续使用现在的string
    }
    
  • 接口设计的时候要考虑数据类型的匹配,比方说map里面有key:country,value为 China/US,如果使用bool值那么只能表示中国或美国一种,但是有一种情况是map里面没有country这个key。换言之直接条件崩了,所以这个时候选择bool表示国家并不好,它表示的是中国或者不是中国,这种设计很糟糕

针对多线程编程的教训

看这个链接https://www.quora.com/How-does-STL-vector-map-etc-work-in-multithreading-environments-in-C++和这个链接https://en.cppreference.com/w/cpp/container#Thread_safety。敲黑板,记住:

  • 读写安全但是添加删除是不安全的。实际上某个数据结构是不是线程安全的就在于可不可以将数据结构的修改/删除/插入linerable
  • 任何使得iterator无效的操作都必须同步化,但是push_back这种倒是对前面的操作无所谓。

针对需求合理性

写这个是因为需求合理性经常被CHALLENGE,所以给出一个综合性的需求文档的设计。

  • 首先需要明确客户是谁,客户的场景是什么样子的,明确客户是谁非常重要,
  • 这个需求究竟是客户提出来的,还是只是顺嘴提一句,SE随手加上的?很多时候SE根本就不懂这个功能,就是胡说八道,因此需要确认是不是真正的需求。(去了qcraft又一次遇到了这个问题,目前遇到的事情是要写一个qsim cache,但是qsim cache具体需要什么功能不确定)
  • 需要明确一点,需求往往是高层的,而具体的解决方案是开发做的,要区分开这两个层面
  • 这个需求是不是SPECIAL的呢?换言之是一个通用性FEATURE还是单独的FEATURE(就这个客户用?),通用性再考虑做。
  • 这个需求是抄谁的?或者参考谁的?原本的场景和现在的场景哪些相同,哪些不同。是不是可以原样复制原本的解决方案?
  • 和我们目前的功能有没有一致性?有没有连续性方面的东西考虑?

从实现角度考虑

  • 细化需求的实现难度,按照实现难度和效益进行排序。过于难/不合理的实现直接拒绝。
  • 对于领导而言,更喜欢更简单,更直接的解决方式

从真实实现角度,在确定需求之前

  • 如果太难解决,就将客户的需求条列化以后和leader讨论一下
  • 和不同的人聊天,看看这个功能有没有类似的,究竟做不做,如果做,从哪个方面做起?为什么不做?

和QA讨论

  • 一个功能,对客户而言应该足够简单,简单才能有人用。

相对应的,这个网址https://www.pmcaff.com/discuss/439444879874112上面的例子很不错,下面截取了一些具体的回复。

CarriePisces的回复

我们不能随意抛弃任何一个需求,不管是否合理,这是基本原则。

拿到这些需求后,做如下步骤:

1、首先看该需求当前的版本是否满足?如果可以满足,是否比当前更优?不能满足是否有替代功能可以满足?如果满足是否可以更优?

2、当前版本功能均不能满足的情况下,一定要跟需求提出者进行一次深入沟通(千万不要一口回绝,即使你觉得非常十分以及特别不合理),了解他提出这个需求的原因、使用场景、带来的意义等,经过反复沟通,你也许会发现有些人的需求跟第一次告诉你会有差异,这是正常的,所以需要沟通沟通再沟通,究其原因。

3、沟通后,你大致有了自己的看法。是否要做,你这时可以结合市场分析、自身产品定位、资源配置等进行整理资料,去说服对方,并且讲明为什么现在不做这样需求。当然了,话不能讲得太满,比如“这个需求经过以下情况分析,balabala~不合理,所以不做”,可以稍微改动下“这个需求经过以下情况分析,balabala~目前看来是不适合做入产品的,但是这是一个好的想法,我们先纳入需求池,根据后期产品发展情况做进一步考虑,只是暂时搁置~”。毕竟,直接否决别人伤和气,再者,有些需求其实只是目前不合理,后期怎样就不一定咯~

4、如果还是不能说服对方的话,23333333,先反省自己,分析得是否合理,语言表达是否清楚,是否有完善的数据支撑。。。如果还是不行的话,没有什么是请吃一顿火锅解决不了的,如果有,那就请吃两顿。#经费自己掏腰包哈#

最后,作为一个产品人,真的不用轻易抛弃和鄙视任何一个需求呀,深挖出实际需求,毕竟,肯给你提需求的是关注你产品的人,不关注的人才懒得理你呢~

嗯,瞎扯完毕。#说的不对的地方指出来哈,一起进步#

知乎的网址也给出了具体的例子,https://www.zhihu.com/question/19804178/answer/274824703

Step1.有效性分析

  • 面向哪些用户(用户有效)?
  • 针对哪些特定的场景(场景有效)?
  • 解决什么问题(痛点有效)?

Step2.可行性分析

  • 与产品先前的设定不出现大的反差
  • 核心功能不能受到影响或者回退
  • .性价比不能太低(这个实际上就对应了成本)

Step3.对比分析

  • 对比竞争对手
  • 对比主流价值观
  • 对比用户行为

这个网址也给出了一些细节https://zhuanlan.zhihu.com/p/23223059。关于具体决定需求对不对使用了一种投票的手段,很不错。

如何编写文档

无论是廖雪峰还是外网的文档,都是从具体规范和格式角度编写文档,而我总结的文档规范主要从内容角度出发。下面给出文档编写流程中的常见考虑方法

  • 首先需要标准化技术术语,避免出现由于中文互联网的不准确性和语义的复杂性导致的错误意义(举个简单的例子,HASH算法中的压缩函数本来应该只是参与计算的一个流程,而有人把HASH本身成为压缩函数)
  • 对文档阅读者建模,主要从以下几个角度来考虑:谁阅读这篇文档,阅读者的教育水平(对专业知识的了解),因此哪些部分要详细?
  • 如果文档当中有客户的需求,要说出来客户最开始的原始需求是什么,需求经过了什么变化,为什么会有这种变化,多方需求要写清楚每一方的变化。
  • 如果文档中有原有功能的局限性,这个需要写具体局限性体现在哪个方面,该功能处于哪一层,为什么要在本层做,便利和优势有哪些,灵活性为什么这样子考虑。
  • 如果文档为设计文档,当中需要有detailed description,务必写清楚我们解决的问题是什么,功能实现的手段,我们做的工作是什么。为了能够方便快速地理解,需要文档编写者遵循一定地框架进行描述:1 分层描述,每层具体提供了什么功能,可以依托现有协议地层次,或者依托功能划分地层次进行描述;2 给出每一层我们依赖地第三方/基础功能,并说明每层对上层提供地保证和利用的下层的依赖。层次之间的够通也需要说清楚。
  • 如果文档针对QA,那么需要给出TestConsiderration,如果是针对开发,要写清楚连贯的逻辑和准则,如果是针对用户,需要给出详尽的使用流程。这里使用流程要说明白,包括详尽的命令提示信息,帮助信息,测试CASE覆盖等多个方面。

  • 上面的部分覆盖了编写的思路,具体的内容我觉得我司的文档模板就写的很不错,包括以下部分:1.1. Problem Statement:;1.1. High Level Overview of this Feature/Function;2. External Requirement;2.1. Customer Requirement;2.1. Usage Scenario;3. Competitor Analysis;4. Function Specification;4.1. Detailed Description of this Feature/Function;4.21.1. Scalability and Performance Target;4.3 Dependencies and Considerations 4.4 Future Enhancements (Optional);5. User Interface 6 Backward Compatibility 7 Platform Support (Optional) 8 Packaging Consideration 9 Security Consideration 10 1. Testing Consideration 11. Troubleshooting Consideration 12 . Issue and Limitation Reference主要是参考

下面的各个部分并不单独针对需求/设计文档,是通用的文档内容。下面列出来详尽的每个部分该写些什么内容。

  • 1.1. Problem Statement: 1. Is there any background information for this problem? 2. What is the problem we are trying to solve? 3. Is there any specific information from customers?
  • 1.2. High Level Overview of this Feature/Function : [This section should cover the high-level function description.]
  • 2.1 Customer Requirement:[This section should cover the details of the customer requirements.]
  • 2.2. Usage Scenario : [This section should cover the usage/configuration steps from the user perspective.]
  • 3.1 Competitor Analysis:\1. Which competitors to compete with?\2. What is the developing status of the industry and competitors?\3. What have the competitors done to meet the same or similar customer requirement?\4. What are their solution’s advantages and disadvantages?
  • 4.1. Detailed Description of this Feature/Function: [This section should cover the following contents: issues with system integration, memory, reliability, forward and/or backward compatibility, etc.]
  • 4.2. Scalability and Performance Target \1. Does this feature have any scalability or performance target?\2. What are the settings of external parameters and limits for this requirement?
  • 4.3. Dependencies and Considerations: \1. What versions of ArrayOS need to support this feature?\2. Does this feature have a client component, and what are the supported client OS? 3. Does this feature require any integration with HA, Clustering, Webwall, Client Security, etc,?
  • 4.4. Future Enhancements (Optional)\1. What is still missing for a complete solution? \2What needs to be done to improve the usability or performance?
  • 5.1 System Administrator [This section should cover the following contents:What does the end user see/experience, for example Array Client UI, Authorization, Portal theme, etc.]
  • 6.1 Backward Compatibility
  • 7.1 Platform Support (Optional)[This section should cover the following contents: Feature Matrix for clients or servers, etc.]
  • 8.1 Packaging Consideration[This section should cover the following contents: How the software can be delivered to the target machine. Is feature control, additional or special HW required? If no, input “None” here.]
  • 9.1 Security Consideration
  • 10.1 Testing Consideration [This section should cover the following contents: Test requirement, equipment, setup, and test scenarios for black box testing. If no, input “None” here.]
  • 11.1 Troubleshooting Consideration [This section should cover the troubleshooting methods or tools, etc.]
  • 12.1 Issue and Limitation [This section should cover the existing issues and known limitations from the external perspective.]
  • 13.1 Reference 各种RFC之类的资料

如果是设计文档,那么在detailed description里面还得加上:[Input the text here.

This section describes the high-level software architecture design/implementation of the internal and detailed external programming interface (API), even with ported codes.

*In addition, the following information MUST be provided if any:*

1. Platform:* *List which platforms this feature/function will be supported on.

2. Boundary conditions:* *Should list some maximum numbers related to this feature/function, such as the maximum number of users.

3. Error handling:* *Should state how errors are handled.

4. Security:* *Security considerations are an important part of a project. We should detail possibilities of abuse of the system.]

A separate*** **Design Document for more detailed internal design can be referenced in case more design details is required,* such as internal data structure, flow chart, algorithm used, exact*** **code, etc.*]

####

一些学习到的常见的小技巧

位运算

最常见的例子应该是linux内核的GFP_ZONE_TABLE,如果ZONES的个数小于8,那么GFP_ZONES_SHIFT为3。具体的看代码每一个OPT_ZONE_XXX实际上就是存在情况下的ZONE_XXX,因此我们能看出来,那么我们可以看到,每个___GFP_XXX对应了一个左移的次数,不同的ZONE对应不同的左移的位数,当找需要计算的GFP_ZONE的时候,需要先获取GFP_FLAGS的低四位。 低四位就是计算的时候想要的___GFP_XXX然后将GFP_ZONE_TABLE右移bit*GFP_ZONES_SHIFT位,最后用(1 << GFP_ZONES_SHIFT) - 1将具体的ZONE的个数的为都置为1。然后取出来一样的。如果还有不明白的建议看这个网页https://richardweiyang-2.gitbook.io/kernel-exploring/00-memory_a_bottom_up_view/12-gfp_usage

ZONE_XXX VALUE
ZONE_DMA 0
ZONE_DMA32 1
ZONE_NORMAL 2
ZONE_HIGHMEM 3
ZONE_MOVABLE 4
ZONE_DEVICE 5
#define GFP_ZONE_TABLE ( \
	(ZONE_NORMAL << 0 * GFP_ZONES_SHIFT)				       \
	| (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT)		       \
	| (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT)	       \
	| (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT)		       \
	| (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT)		       \
	| (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT)    \
	| (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)\
	| (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)\
)

优先级

linux的实时程序和普通进程的优先级很有趣,用了一种超级简单的方式来区分这两种进程的优先级:

  • 实时程序用的优先级是weight = 1000 + p->rt_priority;
  • 而普通程序就是简单的weight = p->counter;if (!weight) goto out; weight += 20 - p->nice;。所以如果该进程的时间片耗尽,那么就讲优先级置成0,在本轮调度中没有机会了。这个方法实际上在《现代操作系统》里面有说。

现在让我们回忆一下操作系统几种常见的调度方式:

  • 最短作业方法
  • FIFO
  • 最短剩余时间

结尾

唉,尴尬

狗头的赞赏码.jpg