What happens when an Arc is cloned? - concurrency

I am learning concurrency and want to clarify my understanding on the following code example from the Rust book. Please correct me if I am wrong.
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
What is happening on the line let data = data.clone()?
The Rust book says
we use clone() to create a new owned handle. This handle is then moved into the new thread.
What is the new "owned handle"? It sounds like a reference to the data?
Since clone takes a &self and returns a Self, is each thread modifying the original data instead of a copy? I guess that is why the code is not using data.copy() but data.clone() here.
The data on the right side is a reference, and the data on the left is a owned value. There is a variable shadowing here.

[...] what is happening on let data = data.clone()?
Arc stands for Atomically Reference Counted. An Arc manages one object (of type T) and serves as a proxy to allow for shared ownership, meaning: one object is owned by multiple names. Wow, that sounds abstract, let's break it down!
Shared Ownership
Let's say you have an object of type Turtle 🐢 which you bought for your family. Now the problem arises that you can't assign a clear owner of the turtle: every family-member kind of owns that pet! This means (and sorry for being morbid here) that if one member of the family dies, the turtle won't die with that family-member. The turtle will only die if all members of the family are gone as well. Everyone owns and the last one cleans up.
So how would you express that kind of shared ownership in Rust? You will quickly notice that it's impossible to do with only standard methods: you'd always have to choose one owner and everyone else would only have a reference to the turtle. Not good!
So along come Rc and Arc (which, for the sake of this story, serve the exact same purpose). These allow for shared ownership by tinkering a bit with unsafe-Rust. Let's look at the memory after executing the following code (note: the memory layout is for learning and might not represent the exact same memory layout from the real world):
let annas = Rc::new(Turtle { legs: 4 });
Memory:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 1 |
+--------+ | data: 🐢 |
+------------+
We see that the turtle lives on the heap... next to a counter which is set to 1. This counter knows how many owners the object data currently has. And 1 is correct: annas is the only one owning the turtle right now. Let's clone() the Rc to get more owners:
let peters = annas.clone();
let bobs = annas.clone();
Now the memory looks like this:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 3 |
+--------+ ^ | data: 🐢 |
| +------------+
peters: |
+--------+ |
| ptr: o-|----+
+--------+ ^
|
bobs: |
+--------+ |
| ptr: o-|----+
+--------+
As you can see, the turtle still exists only once. But the reference count was increased and is now 3, which makes sense, because the turtle has three owners now. All those three owners reference this memory block on the heap. That's what the Rust book calls owned handle: each owner of such a handle also kind of owns the underlying object.
(also see "Why is std::rc::Rc<> not Copy?")
Atomicity and Mutability
What's the difference between Arc<T> and Rc<T> you ask? The Arc increments and decrements its counter in an atomic fashion. That means that multiple threads can increment and decrement the counter simultaneously without a problem. That's why you can send Arcs across thread-boundaries, but not Rcs.
Now you notice that you can't mutate the data through an Arc<T>! What if your 🐢 loses a leg? Arc is not designed to allow mutable access from multiple owners at (possibly) the same time. That's why you often see types like Arc<Mutex<T>>. The Mutex<T> is a type that offers interior mutability, which means that you can get a &mut T from a &Mutex<T>! This would normally conflict with the Rust core principles, but it's perfectly safe because the mutex also manages access: you have to request access to the object. If another thread/source currently has access to the object, you have to wait. Therefore, at one given moment in time, there is only one thread able to access T.
Conclusion
[...] is each thread modifying the original data instead of a copy?
As you can hopefully understand from the explanation above: yes, each thread is modifying the original data. A clone() on an Arc<T> won't clone the T, but merely create another owned handle; which in turn is just a pointer that behaves as if it owns the underlying object.

I am not an expert on the standard library internals and I am still learning Rust.. but here is what I can see: (you could check the source yourself too if you wanted).
Firstly, an important thing to remember in Rust is that it is actually possible to step outside the "safe bounds" that the compiler provides, if you know what you're doing. So attempting to reason about how some of the standard library types work internally, with the ownership system as your base of understanding may not make lots of sense.
Arc is one of the standard library types that sidesteps the ownership system internally. It essentially manages a pointer all by itself and calling clone() returns a new Arc that points at the exact same piece of memory the original did.. with an incremented reference count.
So on a high level, yes, clone() returns a new Arc instance and the ownership of that new instance is moved into the left hand side of the assignment. However, internally the new Arc instance still points where the old one did.. via a raw pointer (or as it appears in the source, via a Shared instance, which is a wrapper around a raw pointer). The wrapper around the raw pointer is what I imagine the documentation refers to as an "owned handle".

std::sync::Arc is a smart pointer, one that adds the following abilities:
An atomically reference counted wrapper for shared state.
Arc (and its non-thread-safe friend std::rc::Rc) allow shared ownership. That means that multiple "handles" point to the same value. Whenever a handle is cloned, a reference counter is incremented. Whenever a handle is dropped, the counter is decremented. When the counter goes to zero, the value that the handles were pointing to is freed.
Note that this smart pointer does not call the underlying clone method of the data; in fact, there may doesn't need to be an underlying clone method! Arc handles what happens when clone is called.
What is the new "owned handle"? It sounds like a reference to the data?
It both is and isn't a reference. In the broader programming and English sense of the word "reference", it is a reference. In the specific sense of a Rust reference (&Foo), it is not a reference. Confusing, right?
The second part of your question is about std::sync::Mutex, which is described as:
A mutual exclusion primitive useful for protecting shared data
Mutexes are common tools in multithreaded programs, and are well-described
elsewhere so I won't bother repeating that here. The important thing to note is that a Rust Mutex only gives you the ability to modify shared state. It is up to the Arc to allow multiple owners to have access to the Mutex to even attempt to modify the state.
This is a bit more granular than other languages, but allows for these pieces to be reused in novel ways.

Related

Smart pointer, why need to check if I am the only user before changing the underlying object?

I am reading C++ Primer and find these kinda confusing:
The reset member is often used together with unique to control changes
to the object shared among several shared_ptrs. Before changing the
underlying object, we check whether we’re the only user. If not, we
make a new copy before making the change:
if (!p.unique())
p.reset(new string(*p)); // we aren't alone; allocate a new copy
*p += newVal; // now that we know we're the only pointer, okay to change this object
What does the emphasized text mean in the quoted text above? So confused.
Update:
After reading the text again, I find out that I may miss something.
So according the code above, let's assume there are 2 shared_ptr (one is p mentioned here) pointing to the original dynamic memory object let's say A. Then if I want to modify object A, I allocate a new dynamic memory with the copy value of A(new string(*p)), assign it to p, let's say B. So eventually A is not modified, but only create a copy of modified version of A?
Why not directly do *p += newVal;? And why is it related to Copy-on-write mentioned in answers? I mean, there's no extra copy operation needed. All shared_ptr originally points to dynamic memory object A. Only 1 object.
Screenshot that may supply a little bit more context:
I think authors of the book described here how Copy-on-write paradigm can be implemented using shared_ptr. As mentioned in comments before "this isn't a requirement of shared_ptr, its simply a design decision".
For you are only allowed to modify the shared_ptr and not the objects they refer to. This is to prevent data races.
From util.smartptr.shared/4:
For purposes of determining the presence of a data race, member
functions shall access and modify only the shared_­ptr and weak_­ptr
objects themselves and not objects they refer to.
Changes in
use_­count() do not reflect modifications that can introduce data
races.
For the reset() member function:
void reset() noexcept;
Effects: Equivalent to shared_­ptr().swap(*this).
Update: Substantially revised, based on new knowledge.
Short answer (to the title of your question): you don't. What C++ primer are you reading? No way is the example you quote there primer material.
The whole idea behind smart pointers is that they 'just work', once you understand them properly, and the author of the passage has pulled a stunt here which would rarely, if ever, be used in practice.
He appears to be trying to describe some sort of oh-so-slightly-weird copy-on-write mechanism implemented in software, but he has clearly bamboozled the OP and no doubt most of the rest of his readership in doing so. It's all a bit silly and it's just not worth trying to understand why they present it as they do (or, indeed, just what it is supposed to do in the first place). Like I say, it has no place in a primer (or probably anywhere else).
Anyway, std::shared_ptr::unique() is flawed (it is not threadsafe) and will be going away soon.
It should probably never have existed in the first place, don't use it.
One other issue arose during various discussions in the thread, and that is whether it is safe to mutate an object managed by a shared_ptr. Well wherever did you get the notion that it isn't? Of course it is. If you couldn't, many programs simply could not be written at all. Just don't mutate the same object from two different threads at the same time (that's what the standard calls a data race) - that's the only issue. If you do want to do that, use a mutex.

How to manage millions of game objects with a Slot Map / Object Pool pattern in C++?

I'm developing a game server for a video game called Tibia.
Basically, there can be up to millions of objects, of which there can be up to thousands of deletes and re-creations as players interact with the game world.
The thing is, the original creators used a Slot Map / Object Pool on which pointers are re-used when an object is removed. This is a huge performance boost since there's no need to do much memory reallocation unless needed.
And of course, I'm trying to accomplish that myself, but I've come into one huge problem with my Slot Map:
Here's just a few explanation of how Slot Map works according to a source I found online:
Object class is the base class for every game object, my Slot Map / object Pool is using this Object class to save every allocated object.
Example:
struct TObjectBlock
{
Object Object[36768];
};
The way the slot map works is that, the server first allocates, say, 36768 objects in a list of TObjectBlock and gives them a unique ID ObjectID for each Object which can be re-used in a free object list when the server needs to create a new object.
Example:
Object 1 (ID: 555) is deleted, it's ID 555 is put in a free object ID
list, an Item creation is requested, ID 555 is reused since it's on
the free object list, and there is no need to reallocate another
TObjectBlock in the array for further objects.
My problem: How can I use "Player" "Creature" "Item" "Tile" to support this Slot Map? I don't seem to come up with a solution into this logic problem.
I am using a virtual class to manage all objects:
struct Object
{
uint32_t ObjectID;
int32_t posx;
int32_t posy;
int32_t posz;
};
Then, I'd create the objects themselves:
struct Creature : Object
{
char Name[31];
};
struct Player : Creature
{
};
struct Item : Object
{
uint16_t Attack;
};
struct Tile : Object
{
};
But now if I was to make use of the slot map, I'd have to do something like this:
Object allocatedObject;
allocatedObject.ObjectID = CreateObject(); // Get a free object ID to use
if (allocatedObject.ObjectID != INVALIDOBJECT.ObjectID)
{
Creature* monster = new Creature();
// This doesn't make much sense, since I'd have this creature pointer floating around!
monster.ObjectID = allocatedObject.ObjectID;
}
It pretty much doesn't make much sense to set a whole new object pointer the already allocated object unique ID.
What are my options with this logic?
I believe you have a lot of tangled concepts here, and you need to detangle them to make this work.
First, you are actually defeating the primary purpose of this model. What you showed smells badly of cargo cult programming. You should not be newing objects, at least without overloading, if you are serious about this. You should allocate a single large block of memory for a given object type and draw from that on "allocation" - be it from an overloaded new or creation via a memory manager class. That means you need separate blocks of memory for each object type, not a single "objects" block.
The whole idea is that if you want to avoid allocation-deallocation of actual memory, you need to reuse the memory. To construct an object, you need enough memory to fit it, and your types are not the same length. Only Tile in your example is the same size as Object, so only that could share the same memory (but it shouldn't). None of the other types can be placed in the objects memory because they are longer. You need separate pools for each type.
Second, there should be no bearing of the object ID on how things are stored. There cannot be, once you take the first point into consideration, if the IDs are shared and the memory is not. But it must be pointed out explicitly - the position in a memory block is largely arbitrary and the IDs are not.
Why? Let's say you take object 40, "delete" it, then create a new object 40. Now let's say some buggy part of the program referenced the original ID 40. It goes looking for the original 40, which should error, but instead finds the new 40. You just created an entirely untrackable error. While this can happen with pointers, it is far more likely to happen with IDs, because few systems impose checks on ID usage. A main reason for indirecting access with IDs is to make access safer by making it easy to catch bad usage, so by making IDs reusable, you make them just as unsafe as storing pointers.
The actual model for handling this should look like how the operating system does similar operations (see below the divide for more on that...). That is to say, follow a model like this:
Create some sort of array (like a vector) of the type you want to store - the actual type, not pointers to it. Not Object, which is a generic base, but something like Player.
Size that to the size you expect to need.
Create a stack of size_t (for indexes) and push into it every index in the array. If you created 10 objects, you push 0 1 2 3 4 5 6 7 8 9.
Every time you need an object, pop an index from the stack and use the memory in that cell of the array.
If you run out of indexes, increase the size of the vector and push the newly created indexes.
When you use objects, indirect via the index that was popped.
Essentially, you need a class to manage the memory.
An alternative model would be to directly push pointers into a stack with matching pointer type. There are benefits to that, but it is also harder to debug. The primary benefit to that system is that it can easily be integrated into existing systems; however, most compilers do similar already...
That said, I suggest against this. It seems like a good idea on paper, and on very limited systems it is, but modern operating systems are not "limited systems" by that definition. Virtual memory already resolves the biggest reason to do this, memory fragmentation (which you did not mention). Many compiler allocators will attempt to more or less do what you are trying to do here in the standard library containers by drawing from memory pools, and those are far more manageable to use.
I once implemented a system just like this, but for many good reasons have ditched it in favor of a collection of unordered maps of pointers. I have plans to replace allocators if I discover performance or memory problems associated with this model. This lets me offset the concern of managing memory until testing/optimization, and doesn't require quirky system design at every level to handle abstraction.
When I say "quirky", believe me when I say that there are many more annoyances with the indirection-pool-stack design than I have listed.

C++ Is making objects depend on others a good design?

I have a basic design that consists of three classes : A Data class, A Holder class wich holds and manages multiple Data objects, and a Wrapper returned by the Holder wich contains a reference to a Data object.
The problem is that Wrapper must not outlive Holder, or it will contain a dangling reference, as Holder is responsible for deleting the Data objects. But as Wrapper is intended to have a very short lifetime (get it in a function, make some computation on its data, and let it go out of scope), this should not be a problem, but i'm not sure this is a good design.
Here are some solutions i thought about:
-Rely on the user reading the documentation, technically the same thing happens with STL iterators
-Using shared_ptr to make sure the data lasts long enought, but it feels like overkill
-Make Wrapper verify its Holder still exists each time you use it
-Any idea?
(I hope everyone can understand this, as english is not my native language)
Edit : If you want to have a less theoric approach, this all comes from a little algorithm i'm trying to write to solve Sudokus, the Holder is the grid, the Data is the content of each box (either a result or a temporary supposition), and the Wrapper is a Box class wich contains a reference to the Data, plus additional information like row and column.
I did not originally said it because i want to know what to do in a more general situation.
Only ever returning a Wrapper by value will help ensure the caller doesn't hold onto it beyond the calling scope. In conjunction with a comment or documentation that "Wrappers are only valid for the lifetime of the Holder that created them" should be enough.
Either that or you decide that ownership of a Data is transferred to the Wrapper when the Wrapper is created, and the Data is destroyed along with the Wrapper - but what if you want a to destroy a Wrapper without deleting the Data? You'd need a method to optionally relinquish ownership of the Data back to the Holder.
Whichever you choose, you need to decide what owns (ie: is responsible for the lifetime of) Data and when - once you've done that you can, if you want, use smart pointers to help with that management - but they won't make the design decision for you, and you can't simply say "oh I'll use smart pointers instead of thinking about it".
Remember, if you can't manage heap memory without smart pointers - you've got no business managing heap memory with them either!
To elaborate on what you have already listed as options,
As you suggested, shared_ptr<Data> is a good option. Unless performance is an issue, you should use it.
Never hold a pointer to Data in Wrapper. Store a handle that can be used to get a pointer to the appropriate Data object. Before Data is accessed through Wrapper, get a pointer the Data object. If the pointer is not valid, throw an exception. If the pointer is valid, proceed along the happy path.

What are the benefits and risks, if any, of using std::move with std::shared_ptr

I am in the process of learning C++11 features and as part of that I am diving head first into the world of unique_ptr and shared_ptr.
When I started, I wrote some code that used unique_ptr exclusively, and as such when I was passing my variables around I needed to accomplish that with std::move (or so I was made to understand).
I realized after some effort that I really needed shared_ptr instead for what I was doing. A quick find/replace later and my pointers were switched over to shared but I lazily just left the move() calls in.
To my surprise, not only did this compile, but it behaved perfectly well in my program and I got every ounce of functionality I was expecting... particularly, I was able to "move" a shared_ptr from ObjectA to ObjectB, and both objects had access to it and could manipulate it. Fantastic.
This raised the question for me though... is the move() call actually doing anything at all now that I am on shared_ptr? And if so, what, and what are the ramifications of it?
Code Example
shared_ptr<Label> lblLevel(new Label());
//levelTest is shared_ptr<Label> declared in the interface of my class, undefined to this point
levelTest = lblLevel;
//Configure my label with some redacted code
//Pass the label off to a container which stores the shared_ptr in an std::list
//That std::list is iterated through in the render phase, rendering text to screen
this->guiView.AddSubview(move(lblLevel));
At this point, I can make important changes to levelTest like changing the text, and those changes are reflected on screen.
This to me makes it appear as though both levelTest and the shared_ptr in the list are the same pointer, and move() really hasn't done much. This is my amateur interpretation. Looking for insight. Using MinGW on Windows.
ecatmur's answer explains the why of things behaving as you're seeing in a general sense.
Specifically to your case, levelTest is a copy of lblTest which creates an additional owning reference to the shared resource. You moved from lblTest so levelTest is completely unaffected and its ownership of the resource stays intact.
If you looked at lblTest I'm sure you'd see that it's been set to an empty value. Because you made a copy of the shared_ptr before you moved from it, both of the existing live instances of the pointer (levelTest and the value in guiView) should reference the same underlying pointer (their get method returns the same value) and there should be at least two references (their use_count method should return 2, or more if you made additional copies).
The whole point of shared_ptr is to enable things like you're seeing while still allowing automatic cleanup of resources when all the shared_ptr instances are destructed.
When you move-construct or move-assign from a shared pointer of convertible type, the source pointer becomes empty, per 20.7.2.2.1:
22 - Postconditions: *this shall contain the old value of r. r shall be empty. r.get() == 0.
So if you are observing that the source pointer is still valid after a move-construct or move-assignment, then either your compiler is incorrect or you are using std::move incorrectly.
For example:
std::shared_ptr<int> p = std::make_shared<int>(5);
std::shared_ptr<int> q = std::move(p);
assert(p.get() == nullptr);
If you copy a shared_ptr, the reference count to the pointer target is incremented (in a thread-safe way).
Instead, when you move a shared_ptr from A to B, B contains the copy of the state of A before the move, and A is empty. There was no thread-safe reference count increment/decrement, but some very simple and inexpensive pointer exchange between the internal bits of A and B.
You can think of a move as an efficient way of "stealing resources" from the source of the move to the destination of the move.

Handles vs Smart pointers. What to use?

I'm starting to develop a graphical engine just for practicing purposes. One of the first questions that arised is either to use handles or smart pointers to refer to my class instances.
From my point of view:
Smart pointers pros: created under demand, they do not have the problem of becoming stale pointers; cons: as they are in a linked list, searching for a pointer is an O(n) operation.
Handles pros: search is O(1), object relocation is O(1); cons: can became stale pointers, creating a new handle forces the system to check for the first NULL entry in the handles table.
Which one to choose? Please explain your selection.
EDITED:
I want to clarify some points after your comments and answers.
I don't mean smart pointers are a linked list in the way of "are represented by a STL linked list". I mean they behave, in some way as a linked list (if you move one object from one memory block to another, you need to iterate the full list of smart pointers to update all references to this object properly -it can be done with a linked list -).
And I don't mean handles exactly as opaque pointers or pointer to implementation models. I mean having a global handle table (an array of pointers) so when I request an object, I get a dereferenceable instance containing the index in this table where the actual pointer to the object can be found. So, if I move the object from one block to another, just updating the pointer entry in the handle table I get all pointers automatically updated at the same time.
Neither of those definitions fit what's normally used. Smart pointers aren't in a linked-list in any way at all. Usually you use the observer pattern to keep a vector of raw pointers to objects that still exist if you need to iterate them or something. Handles as you describe them are pretty much only used for binary compatibility reasons and never in-process.
Use smart pointers, they take care of themselves.
The term "handle" is a broad term that, essentially, means an identifier to an object.
A pointer or smart pointer falls under this definition, so you need to pick a terser term for your Option 2.
"Handle"
|
/------+-------\
/ | \
/ | \
Pointer Reference Other Identififer
| | \
|----+----| `T&` \
| | |---+------|
`T*` `shared_ptr<T>` Text Number (e.g. HWND in WinAPI)
If I assume that you mean some fixed, memory-abstracted "other identifier" then, sure, you can employ this. You don't necessarily have an either/or scenario here. You probably want to use smart pointers anyway (for lifetime management if nothing else), and smart pointers don't need to be in a linked list.
You could have a std::map<your_identifier_type, std::shared_ptr<T> > to map your fixed, user-defined identifier to a [potentially-changing] smart pointer.
Disclaimer: This diagram was hastily drawn and represents my vision of the terminology tree as it stands now, half an hour after getting out of bed. There may be minor discrepancies with other views, but it should give a fairly reliable impression of things.