【问题标题】:Perl: JSON fails if a thread is startedPerl:如果启动线程,JSON 将失败
【发布时间】:2018-03-29 08:47:01
【问题描述】:

如果启动了某个线程,谁能告诉我为什么 JSON 不起作用?

use strict;
use warnings;
use JSON;
use threads;
use threads::shared;

sub th {   }

threads->create(\&th)->join() if $ARGV[0];

my $json = to_json({ val => "123"});           # WTF?!?
print "$json\n";

工作正常并打印 JSON 字符串。但是将1 作为参数传递给脚本以创建线程,to_json 将失败并显示

hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this)

如果我使用 encode_json insead,效果相同。 在 JSON 的联机帮助页上,没有出现 thread 这个词,我看不出为什么线程应该损害外部字符串转换。

???

【问题讨论】:

  • 我的第一个想法是它与解析器相关。要么,要么线程调用与 JSON 模块的全局状态混淆。

标签: json multithreading perl


【解决方案1】:

JSON(.pm) 只是 JSON::PP、JSON::XS 或 Cpanel::JSON::XS 的前端。

您在 JSON::XS 中发现了一个错误。关于这一点,JSON::XS 的文档说:

(I-)线程

不保证此模块是 ithread(或 MULTIPLICITY-)安全的,并且没有计划对此进行更改。请注意,perl 内置的所谓的 theeads/ithreads 已正式弃用,不应使用。

[注意最后一部分是不正确的。官方的立场其实是:线程很难,所以你应该改用别的东西。这是非常值得怀疑的,因为可以说替代方案同样困难。]

解决方法:使用其他后端之一(直接或通过 JSON(.pm))。

$ PERL_JSON_BACKEND=JSON::XS 46793885 0
{"val":"123"}

$ PERL_JSON_BACKEND=JSON::XS 46793885 1
hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this) at /home/ikegami/usr/perlbrew/perls/5.26.0t/lib/site_perl/5.26.0/JSON.pm line 170.

$ PERL_JSON_BACKEND=Cpanel::JSON::XS 46793885 1
{"val":"123"}

$ PERL_JSON_BACKEND=JSON::PP 46793885 1
{"val":"123"}

您可以在脚本中通过在加载 JSON 之前添加以下内容来控制这一点:

BEGIN { $ENV{PERL_JSON_BACKEND} = 'Cpanel::JSON::XS' }

【讨论】:

    【解决方案2】:

    我也遇到了这个问题(尝试将 JSON 与多线程 perl 一起使用)。在不启动后台线程的情况下,我的代码可以正常工作,但出现与启动线程时相同的错误。

    和你一样,我没有在网上找到任何关于这个错误文本的线程的帮助。但是,在allow_nonref 错误文本之后,我在 JSON::XS 的文档中找到了以下内容:

    “旧”VS。 “新”JSON(RFC 4627 与 RFC 7159)

    TL;DR:出于安全考虑,默认情况下 JSON::XS 不允许 JSON 文本中的标量数据 - 您需要创建自己的 JSON::XS 对象并启用 allow_nonref:

    my $json = JSON::XS->new->allow_nonref;
    
    $text = $json->encode ($data);
    $data = $json->decode ($text);
    

    在您的情况下,您尝试调用 to_json,它在内部创建一个 JSON 对象并在其上调用 encode。不幸的是,它没有在其构造函数中指定allow_nonref。因此,要使您的代码正常工作,您可以执行以下操作:

    use JSON::XS;
    
    my $json_obj = JSON::XS->new->allow_nonref;
    my $json = $json_obj->encode({ val => "123"});
    print "$json\n";
    

    我在阅读此处的其他回复之前提出了此解决方案,因此这些可能是更好的解决方案,但这至少可以让您通过最小的更改来解决问题。

    【讨论】:

    • 在我们的代码中的许多地方使用此解决方案后,我在另一个模块中遇到了一个无法简单修改的​​问题。回顾 ikegami 的解决方案,我相信它更加强大,并且对我来说非常有效。
    • 感谢比尔的修复。我花了 2 天时间解决 pycharm perl CamelDb 插件的问题。
    【解决方案3】:

    这肯定与 JSON 和全局状态有关。 如果你 requireimport JSON,在线程调用之后,它'工作'。

    模块中JSON::XS 的警告包括:

    (I-)线程 ^

    不保证此模块是 ithread(或 MULTIPLICITY-)安全的,并且没有计划更改它

    非线程安全模块的“解决方法”是通过use(发生在“编译”时)加载它,而是通过requireimport(在运行时)在程序的并行实例(线程)启动之后。

    例如:

    use strict;
    use warnings;
    
    use threads;
    use threads::shared;
    
    sub th { }
    
    my $th = threads->create( \&th )->join;
    
    ## Just inside main thread
    ##can do this within individual threads instead if desired
    require JSON;
    JSON->import;
    
    
    my $json = to_json({ val => "123" });    # WTF?!?
    print "\n$json\n";
    

    【讨论】:

    • 不,为什么要扭曲自己继续使用这样一个错误的模块!使用维护的 Cpanel::JSON::XS 而不是 JSON::XS! Cpanel::JSON::XS 是 JSON::XS 的一个分支,它的创建是因为 JSON::XS 的维护者很难处理并且拒绝修复诸如此类的问题!
    • 如果没有其他选择,更多的是提供一般的解决方法。
    • 对于这个问题来说这是一个非常糟糕的解决方案,但它可能对其他人有用。您应该创建一个自我回答的问答。 (示例标题:“带线程的线程不安全模块”。示例引导线:“是否仍然可以在线程内使用不是线程安全的模块?”)
    猜你喜欢
    • 1970-01-01
    • 2013-09-21
    • 1970-01-01
    • 1970-01-01
    • 2013-03-04
    • 2023-01-25
    • 2018-05-15
    • 2015-09-30
    • 2015-04-16
    相关资源
    最近更新 更多