彭于斌
彭于斌
不会再有线程2给refcnt加1了,因为线程2要给refcnt加1,就只有在sharedptr的拷贝构造函数里。因为refcnt归零,就说明没有任何其他线程持有同样的sharedptr对象,连sharedt对象都没有,也就没有任何其他线程有机会调用sharedptr的拷贝构造函数。
refcnt归零说明在这个时刻没有其它线程持有同样的sharedptr对象 既然其他线程已经没有sharedptr对象了,那么他如何调用拷贝构造函数?拷贝构造函数的需要有一个对象的情况下,才能调用的。 sharedptr(sharedptr const &that) 其他线程没有sharedptr对象,那么这个that参数哪里来? 如果你是说当前线程正在析构的sharedptr对象,同时被其他线程拷贝,那是不允许的,因为标准只保证控制块SpCounter的原子性,不保证sharedptr对象的原子性。 任何类型的对象,都不能由线程1析构的同时,线程2调用拷贝构造函数,这从来都是是未定义行为,不仅仅是sharedptr。 无法顺畅的大口呼吸,是活着的最好证明 ---原始邮件--- 发件人: ***@***.***> 发送时间: 2024年9月8日(周日) 上午9:48 收件人: ***@***.***>; 抄送: ***@***.******@***.***>; 主题: Re: [parallel101/stl1weekend] 关于sharedptr thread-safe实现的问题 (Issue #4) 不会再有线程2给refcnt加1了,因为线程2要给refcnt加1,就只有在sharedptr的拷贝构造函数里。因为refcnt归零,就说明没有任何其他线程持有同样的sharedptr对象,连sharedt对象都没有,也就没有任何其他线程有机会调用sharedptr的拷贝构造函数。 对,我理解,引用计数+1只发生在拷贝构造函数里(如果我没看错的话)。但refcnt归零只能说明在这个时刻没有其它线程持有同样的sharedptr对象,然而执行delete有时间间隔,在此间隔可能有线程2执行拷贝构造,换句话说: 线程1判断1成立并将引用计数归零(整体原子操作) 线程2执行拷贝构造,故引用计数+1,也就是说此时有其它线程持有该sharedptr...
这样吧,我也听不懂你在讲什么,你来写一份你认为会产生问题的代码,让我分析,例如: ```cpp shared_ptr a = make_shared(); void t1() { a = nullptr; // 析构a } void t2() { auto b = a; // 拷贝a到b } ```
是的,这段代码有未定义行为! 然而 C++ 标准只要求了: 析构+拷贝 同时发生,是未定义行为。 拷贝+拷贝 同时发生,是安全的。 我的原子变量已经保证了 拷贝+拷贝 的安全,符合 C++ 标准的要求。 析构+拷贝 的情况,C++ 标准就并不要求安全,所以我的 shared_ptr 也没有责任去保证这种情况下的安全。
比如标准不要求 vector 的 clear 和 push_back 同时调用是线程安全的,那么我就不需要把 vector 实现为安全的。 如果标准规定了哪两个函数同时调用是安全的,我再去做。 比如标准就规定了 size 和 data 两个函数同时调用是线程安全的,我只需要符合这个就可以。 标准都没有规定必须安全的情况,我的容器如果产生未定义行为,我不负责任。
例如,C++ 标准对 `shared_ptr` 的要求: 析构+拷贝 同时发生,是未定义行为。 拷贝+拷贝 同时发生,是安全的。 C++ 标准对 `atomic` 的要求: 析构+拷贝 同时发生,是安全的。 拷贝+拷贝 同时发生,是安全的。 所以,只有当我是在实现atomic_shared_ptr时,才需要考虑你说的这种情况,而我现在实现的是shared_ptr,不需要考虑 析构+拷贝 的安全。
为什么拷贝+拷贝是安全的?我怎么没看到cppreference说?这很复杂,是另一句话里透露的通用规则,适用于所有容器,包括shared_ptr、unique_ptr、vector等全部的容器: 两个const成员函数,同时发生,没有未定义行为。 一个非const成员函数+一个const成员函数,同时发生,是未定义行为。 这句话自动适用于所有的容器了,所以你看到shared_ptr里没有说,但是我知道他是在另一个关于线程安全的页面上。
那么很明显,拷贝构造函数`shared_ptr(shared_ptr const &that)`是const的(对于被拷贝的that),而析构函数都是非const的,所以如果没有特别说明,一个容器同时调用拷贝+析构是未定义行为。而atomic_shared_ptr就属于特别说明了,所以他特别地同时访问const和非const函数是安全的。
完整的多线程安全规则表: 读+读=安全 读+写=未定义行为 写+写=未定义行为
所以实际上sharedptr所谓的“线程安全”,只不过是拷贝+拷贝这一情况的安全和拷贝+析构不同`shared_ptr`实例,同一个`shared_ptr`的并发非const访问是没保证的,`shared_ptr`指向的那个`T`也是不保证的(由`T`的实现者“你”来保证)。 `shared_ptr`不是有三层吗?通俗的说就是他只需要保证中间这层控制块的线程安全性,不保证`shared_ptr`对象和`T`对象的安全性。