正所谓“说曹操曹操到”吧,这世间的很多事儿着实架不住念叨。上文书我们刚提到一个“活久见”bug,如今又和他的表妹不期而遇了。
相遇过程如下:
在已经被本地化的产品中选择Preview,Schedules等tab,日文UI无任何异常。
紧接着点击Downloads tab,注意!这里的页面发生了局部刷新,然后我们就猝不及防的看到这样日文和英文夹杂着的怪咖。
对于该问题,测试和开发的确是大费周章,以至于极度怀疑是因为性能负载问题导致了产品的功能异常,为此还特意引入了JMeter,也进行了不止一次的高并发测试来确定病根。
从这张并发测试图上我们可以依稀的看出,那时开发和测试那无助的眼神和表情,但这样的测试结果依旧是然并卵的。
读过上篇文章的同学应该大抵能想到这种情况多半是由于线程安全引发的了吧。确实如此,虽然技术实现上并不完全相同。首先这里不是用Python实现,而是C#,所以自然没有了@classmethod,但[ThreadStatic]又是何等的异曲同工。(示意代码中展示就是真凶的正面照)
我们都知道,类的静态成员变量在使用时,会在该类的多个实例间共享,即便在多线程场合下也不例外。但如果我们需要创建每个线程时,拥有自己的静态变量该如何做呢?千呼万唤始出来,是时候掀起 [ThreadStatic]的盖头了。
不解释,运行后再说。
这段代码应该已经充分展示ThreadStatic和Staic的异同。无论循环多少次,各线程ThreadStaic的数值保持如初,但Static值统统变成了6。而在本案中,globalization context被存储在了LanguageLabel类中的一个ThreadStatic变量里。当IIS收到来自浏览器的请求时,globalizationcontext被写入当前线程。当IIS响应输出时(调用业务逻辑、存储过程等)context被应用到该输出,并将所有当前语言的输出都发送给请求的用户。只要方法是同步的,就可以完美地工作。
然而,对于异步方法,IIS则不一定保留原线程。IIS在遇到异步任务(如存储过程)时会释放线程,然后在控制返回到IIS时请求新线程。我们可能会得到相同的线程或全新的空闲线程。一不小心,就不能保证存储在线程中的context与初始线程的context相同,就像演示代码中所做的那样,第二个线程(en-US)的ThreadStatic值是6,而不是第一个线程(ja-JP)的8,无论运行和循环多久。
一定有人琢磨,这么多风险为啥还要用呢?正是由于处于性能提升的考虑,异步方法已经成为了当下的标准,并将不断鼓励所有开发团队使用异步函数。随着越来越多的页面转移到异步,在高流量,高并发环境中,该问题应该会愈发明显。而对于国际化相关逻辑,我们必须重新设计以保证其线程安全性。
最后,再武断的抛个断言吧——未来在产品code review的过程中,只要看到了g11n相关的context被放在了classmethod、ThreadStatic这样的decorator或attribute内,不用测试就可以直接报bug了。当然啦,俺也坐等各位对此有研究,感兴趣的同学来举反例打脸 ︿( ̄︶ ̄)︿
领取专属 10元无门槛券
私享最新 技术干货