Date: Mon, 24 Feb 2025 06:46:05 +0000
> It is do-able and it could be useful where you have something already committed to single ownership but you really need another thread to see it in action rather than work with a copy. It is going to be rare that the lock actually gets hit by the owning thread but if the calling thread holds that lock for a long time, the chances of the lock being hit increase and when it is hit, the lock could be held for some time. In background processing that may not matter but any perceptible lock out of the GUI would be a problem. In real time systems a locked thread could be fatal.
But think of the implications.
Note. Only the delete would get locked for a time.
If that happens it’s because you are trying to delete something that is being used.
Blocking here is literally what is required, if you don’t its game over.
> The problem with this approach is that it involves a large increase in overhead, storing locks and setting them and it significantly increases the complexity of using it. All of this just so it can handle rare cases where you find yourself locked into an awkward design corner. Meanwhile the intended use that doesn't involve cross thread dereferencing has to carry all that baggage.
Then don’t make it lockable, don’t bother to make it thread safe and clearly document that instead, let the user provide their own safety.
It is still a design that makes sense, it’s just for a different use case.
There’s no need to add the extra overhead of getting and comparing thread ids.
Just pick one of the lanes.
From: JOHN MORRISON <inglesflamenco_at_[hidden]>
Sent: Monday, February 24, 2025 12:33 AM
To: Tiago Freire <tmiguelf_at_[hidden]>
Subject: Re: [std-proposals] A non-owning but self zeroing smart pointer for single ownership
Reposting to see if I can get it to go to the right place
Tiago,
I think it could be made to work. The idea had crossed my mind but you have fleshed out some of the details. I guess setting the lock on a ptr_to_unique would release something you can work with, another type of smart pointer or even a raw pointer.
It is do-able and it could be useful where you have something already committed to single ownership but you really need another thread to see it in action rather than work with a copy. It is going to be rare that the lock actually gets hit by the owning thread but if the calling thread holds that lock for a long time, the chances of the lock being hit increase and when it is hit, the lock could be held for some time. In background processing that may not matter but any perceptible lock out of the GUI would be a problem. In real time systems a locked thread could be fatal.
The problem with this approach is that it involves a large increase in overhead, storing locks and setting them and it significantly increases the complexity of using it. All of this just so it can handle rare cases where you find yourself locked into an awkward design corner. Meanwhile the intended use that doesn't involve cross thread dereferencing has to carry all that baggage. Considerably more baggage than my thread checking mechanism. I appreciate that it would be there to enable something (though not what you usually need) whereas the my thread checking is only there to stop you doing it. This would have conflicting design goals because one goal compromises and complicates the other.
I would call it ts_unique_ptr (ts for threadsafe) which you would use for those use cases where there is a need for a thread to have live access to a unique_ptr in another thread. It could be useful but I think the need for it is less urgent and I don't think ptr_to_unique having to do this is a good solution.
Having a ptr_to_unique and a ts_ptr_to_unique could help though to nudge programmers to think about the domain they are working and choose the one that fits.
________________________________
From: Tiago Freire <tmiguelf_at_[hidden]<mailto:tmiguelf_at_[hidden]>>
Sent: Saturday, February 22, 2025 10:00 AM
To: std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]> <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>>
Cc: JOHN MORRISON <inglesflamenco_at_hotmail.com<mailto:inglesflamenco_at_[hidden]>>
Subject: RE: A non-owning but self zeroing smart pointer for single ownership
I don’t think thread control is the way to go. But I know how you can salvage this, just keep reading to the end.
It’s not true that usage of unique_ptr is exclusive to single thread.
It’s a common to see unique_ptr being accessed all the time by separate threads concurrently, this is not a problem, as long as that access can be assured to not destroy the object. Access only occurs within certain lifetime guarantees.
This can be assured by using a form of lock that either protects the unique_ptr, or what is actually far more common the unique_ptr is part of a larger object and the access and destruction of that larger object is mediated by a synchronization mechanism.
It is also a pattern that I often encounter where objects are created in one thread (which may contain a unique_ptr) but then another thread process it and destroys it.
Note that the following is true:
You want to allow the creation of an owning_ptr and a weak_ptr, and use the weak_ptr, and all of this to work on the same thread.
And here is the critical question:
Can an owning pointer created by thread A be destroyed by thread B?
If the answer is No. Then you can’t have objects being created on one thread and being sent to another for process. It becomes a high-risk trap, your proposal is dead.
If the answer is Yes. Then thread B can just delete it while thread A has a weak_ptr in use and there’s nothing you can do, your proposal is dead.
We seam to be in a conundrum here, or are we?
The reason why shared_ptr and weak_ptr work is not because we have these 2 objects fulfilling a role, but because we have 3 objects each fulfilling a different role, it just so happens that one of the classes plays 2 roles simultaneously.
You have the original shared_ptr (that manages the life time of the object), we have the weak_ptr that serves as an identity token (what and where), but you don’t access the owned object by the “weak_ptr”, in order to do that you need to create a 3rd object (let’s call it the “mediator”) than when created gives you access to the owned object and simultaneously prevents it from being deleted which is played by “shared_ptr”
What you need is a “mediator”, and this is how it works.
Your owning_ptr owns an object and a lock, that lock is acquired before destroying the owned object.
The “weak_ptr” does not provide access to the object but allows you to create a “mediator”.
The “mediator” functions both as scope-lock and as an access to the owned object.
If the object has not been destroyed, the “mediator” will acquire the lock and prevent the owning_ptr from destroying the owned object, releasing it when the mediator goes out of scope. The mediator should not be copiable or transferable.
If the object been destroyed, then it will not acquire the lock and you can test that it has not been acquired.
This is thread safe. It can still potentially lead to a dead-lock if you try to destroy the owning_ptr while you have acquired your “mediator” in the same thread. But this condition is no different than trying to acquire a mutex twice, the user should avoid doing this, plus you can test for this.
Although this has danger zones, those are manageable acceptable. Not so much “which thread owns what” but “how things happen”.
A detail that you might want to address is that you may want to make this “special lock” to be acquired by multiple mediators simultaneously without blocking each other, and only the last one is destroyed it can then be released.
It may also be possible to have a design where the same lock can be shared across multiple owning_ptr, maybe useful when having an array of these and you want an efficient way to manipulate a collection of them. But that is extra.
What do you think?
From: Std-Proposals <std-proposals-bounces_at_[hidden]<mailto:std-proposals-bounces_at_[hidden]>> On Behalf Of JOHN MORRISON via Std-Proposals
Sent: Friday, February 21, 2025 11:29 PM
To: std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>
Cc: JOHN MORRISON <inglesflamenco_at_[hidden]<mailto:inglesflamenco_at_[hidden]>>
Subject: Re: [std-proposals] A non-owning but self zeroing smart pointer for single ownership
Tiago,
You are right in a saying that thread ownership isn't a thing. There is only thread execution and that is why the compiler can't help here and there is no declarative solution. It is also true that a unique_ptr doesn't necessarily run in the thread in which it is created. The thread checking mechanism does anticipate that. It associates a thread id with a unique_ptr<T, notify_ptrs<T> > when the first ptr_to_unique is taken from it and a control block is allocated. That works out quite well. I have been wanting to share how this works:
For thread A to pass a ptr_to_unique to thread B, thread A first has to create a ptr_to_unique to pass which causes a control block to be allocated stamped with thread A. Trying to use it from thread B will trigger an exception.
When thread A moves or swaps a unique_ptr<T, notify_ptrs<T> > held object into thread B, the move or swap is carried out by thread A but all ptr_to_uniques that reference it are zeroed. It is virgin again and no longer associated with a control block. There won't be one to stamp until it is running in thread B and a ptr_to_unique is taken from. It will then create a control block stamped with thread B and all use from thread B will work as expected.
I thinks its cool because it stops what you don't want to happen but doesn't impede what you do want to happen …... as far I have been able to anticipate. But its logic is circumstantial and doesn't have a solid foundation. It is a band aid just to stop you doing the wrong thing and there is something lame about that. That exception will only hit you once. After that you won't go there again.
I believe you when you say that it will be rejected for that.
Perhaps the correct way to look at it is to say that ptr_to_unique works with unique_ptr (declared with the notify_ptrs deleter), a unique_ptr can only have single thread visibility and we have always had to conform to this. So there you have clear domain of safe use where your use of unique_ptr will already be sitting and where the use cases can be found.
If you want data to be safely visible to multiple threads then it must have shared ownership and you will be using shared_ptr/weak_ptr for that, not unique_ptr.
So why complain that ptr_to_unique can't do threadsafe cross thread dereferencing when a unique_ptr is never put in that position and we already have shared_ptr/weak_ptr to do that. It is like complaining that a cat doesn't bark.
I like that argument but how do others see it? Has the concern about thread safety just been a knee jerk reaction that missed the point?
I am not quite sure where to leave it. I will spend more time thinking about it and listen to what others have to say
Received on 2025-02-24 06:46:11