Rails 注意 I18n.locale 是 thread-local

bianjp · 2016年12月05日 · 最后由 bianjp 回复于 2016年12月30日 · 5368 次阅读
本帖已被管理员设置为精华贴

公司某个项目要做多语言支持了,由于我不确定 I18n.locale 是不是全局变量,所以查了下源代码,发现 I18n.locale 是保存在 thread-local variable 中的:

# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
def locale=(locale)
  I18n.enforce_available_locales!(locale)
  @locale = locale && locale.to_sym
end

Reference:https://github.com/svenfuchs/i18n/blob/v0.7.0/lib/i18n/config.rb#L11

为什么不使用全局变量?

大概是为了在多线程环境(比如 puma)中避免请求之间相互影响。

多线程环境下,多个请求是可以并行被不同线程处理的,若使用全局变量,一个请求中修改了 I18n.locale,其它线程正在处理的请求也会受影响。

有什么影响?

1. 若在一个请求中修改了 I18n.locale其它线程的后续请求不会受影响,当前线程的后续请求可能会受影响

  • 若每个请求中都明确设置了 I18n.locale,则不受影响
  • 若后续的请求没有设置 I18n.locale,那么 I18n.locale 的值不是配置文件中配置的默认值,而是之前的请求设置的值

然而请求一般是被随机的分配给各个线程处理的,所以很难说哪个请求会受影响。

2. 设置 I18n.locale 后,各处都可放心地使用 I18n.locale。比如在 Model 中给属性加个 wrapper 方法,根据语言返回相应的字段

结论

若要在 controller 中设置 I18n.locale,各种条件下都要明确设置 I18n.locale,不要只在满足某些条件时才设置。

huacnlee 将本帖设为了精华贴。 12月05日 13:44

发现官方的 Rails Internationalization (I18n) API 指南中也有提到,之前没细看,没发现。

补充一个Time.zone=(time_zone)也是的

这个设计很好,之前用了 china_sms 用的就是全局变量,导致 china_sms 不能同时支持 2 个以上的短信平台

没必要为每个 action 设置 locales,偶觉得更好的方法是使用 around_action + I18n.with_locale,遵从谁申请谁释放的原则就可以啦。具体可以参考这篇文章 Working with Locales and Time Zones in Rails

#6 楼 @0x005a 看起来确实是个更好的方案。

Reference:

关于 Thread-local

不知道大家如果用 redis 是直接

$redis = Redis.new

# 还是

Thread.current[:redis] = Redis.new

#9 楼 @xguox redis 相关的 gem 里面就已经是包裹在 thread 里面了。你可以去看一下 redis-rb 的源码。

使用 Thread.current 在支持多线程的服务器下还是有问题的,建议大家使用gem 'request_store'.

#11 楼 @grantbb 只要一个线程不交叉处理多个请求,就没什么问题。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号