Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DI并发获取, 存在协程切换 可能会获取到不同实例 #5116

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

tw2066
Copy link
Contributor

@tw2066 tw2066 commented Sep 30, 2022

最近看到这个文章 https://blog.csdn.net/lqb3732842/article/details/126559432
测试了一下, 并发下获取类, 可能出现获取到不同实例

测试类

    public function testDi()
    {
        go(function (){
            $goods = ApplicationContext::getContainer()->get(GoodsInfoService::class);
            var_dump($goods);
        });
        go(function (){
            $goods = ApplicationContext::getContainer()->get(GoodsInfoService::class);
            var_dump($goods);
        });
        go(function (){
            $goods = ApplicationContext::getContainer()->get(GoodsInfoService::class);
            var_dump($goods);
        });

        sleep(3);
    }

普通类

class GoodsInfoService
{
    public function __construct()
    {
        //模拟IO
        sleep(1);
    }
}

结果

获取对象不一致

object(App\Service\GoodsInfoService)#3112 (0) {
}
object(App\Service\GoodsInfoService)#3107 (0) {
}
object(App\Service\GoodsInfoService)#3102 (0) {
}

修复后 正常

object(App\Service\GoodsInfoService)#3112 (0) {
}
object(App\Service\GoodsInfoService)#3112 (0) {
}
object(App\Service\GoodsInfoService)#3112 (0) {
}

@tw2066 tw2066 changed the title DI并发获取, 引发协程切换 出现获取到不同实例 DI并发获取, 存在协程切换 可能会获取到不同实例 Sep 30, 2022
if (isset($this->resolvedEntries[$id]) || array_key_exists($id, $this->resolvedEntries)) {
return $this->resolvedEntries[$id];
}
Coroutine::sleep(0.001);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里可以改为用 usleep

@huangzhhui huangzhhui self-assigned this Oct 6, 2022
@chenjian-cpu
Copy link
Contributor

为啥不用协程锁?

@twose
Copy link

twose commented Oct 8, 2022

建议改成用channel广播,即有一个协程在获取中时,将标志位置为true,如果此时有更多的协程也需要获取,就去创建channel,首个获取协程返回时,如果检查到有其它等待获取的协程(即channel被创建了),就往这个channel里push获取到的对象直到channel没有订阅者。

或者,后来的协程使用yield挂起,并记录到一个队列里,当第一个获取的协程获取完成后,挨个去resume那些等待的协程。

总之,sleep的轮询检查的方式非常低效,而且很不优雅。

@tw2066
Copy link
Contributor Author

tw2066 commented Oct 8, 2022

在框架启动时 ,Di组件 没有在协程环境

@twose
Copy link

twose commented Oct 8, 2022

在框架启动时 ,Di组件 没有在协程环境

Swow下没有非协程环境,主协程和其它手动创建的协程都是平等关系;
Swoole下比较麻烦,要么得把启动阶段套在Co\run()里,要么如果非协程环境总是第一个去获取对象的,可以通过临时创建一个协程去push的方式广播给其它协程,但无法实现订阅。

@limingxinleo
Copy link
Member

这里不能改,di 读取不能增加协程逻辑,对 di 来说这就是一个对象,如果你把 io 扔到了构造函数里,就算这里改了,其他地方用的时候也还是有相同的问题。

@tw2066
Copy link
Contributor Author

tw2066 commented Oct 11, 2022

这里不能改,di 读取不能增加协程逻辑,对 di 来说这就是一个对象,如果你把 io 扔到了构造函数里,就算这里改了,其他地方用的时候也还是有相同的问题。

比如一下第三方的组件, 构造函数可能存在io; 这样改 可以确定 获取到的是同一个实例

其他地方是指哪方面呢?

@limingxinleo
Copy link
Member

如果 构造函数里有 io 的类,你就在进程启动后,立马进行初始化就行了

@assert6
Copy link
Member

assert6 commented Mar 22, 2023

如果 构造函数里有 io 的类,你就在进程启动后,立马进行初始化就行了

他是担心有些组件构造函数里有IO, 但是他自己也不清楚;
我也不建议这样搞, 但看了下Spring 的容器是线程安全的, 那我们是否可以提供一个协程安全的组件解决这个问题🤔

@anynone
Copy link

anynone commented May 5, 2023

swoole channel有可能阻塞,而且和swoole版本有关,非常奇怪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants