Why the slave thread can still use the object since the main thread has dropped it?

use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;

fn main() {
     let counter = Arc::new(Mutex::new(5));
     let bor = Arc::clone(&counter);
     let handle2 = thread::spawn(move || {
        for _i in 0..10 {
            let mut num = bor.lock().unwrap();
            println!("num is: {}", num);
            *num += 1;
            thread::sleep(Duration::from_secs(1))
        }

    });
    thread::sleep(Duration::from_secs(3));
    println!("counter has been changed to {:?} by the slave thread", counter.lock().unwrap());
    println!("drop counter");
    drop(counter);
    handle2.join().unwrap();
}

output:
num is: 5
num is: 6
num is: 7
num is: 8
counter has been changed to 9 by the slave thread
drop counter
num is: 9
num is: 10
num is: 11
num is: 12
num is: 13
num is: 14

From the docs:

The type Arc<T> provides shared ownership of a value of type T , allocated in the heap.
[...]
When the last Arc pointer to a given value is destroyed, the pointed-to value is also destroyed.

It's shared ownership. Even if the main thread drops its own Arc, the other thread still keeps it alive.

In fact, that is the whole point of a reference-counted pointer. The pointed object goes away when (and only when) everyone pointing to it has resigned ownership.

1 Like

If you want the thread to lose access to the value inside the Arc, you can use a Weak in the places that shouldn't own it. You can create a Weak using Arc::downgrade.

Then, what's the difference between this mechanism and a direct copy?
Is the data processed by different threads still the same data?

The data is the same, yes. That's why you need Mutex, RwLock or some other way to make it Sync - otherwise you'll eventually end up with data race.

If it is the same data, then why does my main thread release it, and the slave thread can still use it ?

If I delete one and the rest can continue to exist, then I change one and how the rest will change (if this Arc is mutable)? Feeling logically a bit unbalanced, a bit difficult to understand

OK, this looks like it deserves some more verbose explanation.

There are two layers of data here:

  • Arc, which is essentially the pointer;
  • Mutex, which is the wrapper around your "real data".

When you write Arc::clone, you get two pointers (of the type Arc) to the same Mutex. One of this pointers is then moved into the slave thread, another one is held by the main thread. The Mutex during this operations remains wherever it was - this is the main point or Arc/Rc: multiple pointers to the same place.

When the main thread drops the Arc, it, again, drops the pointer, not the real data. Real data are stored in some shared memory and dropped only when all pointers to them are dropped, and, since the copy of the Arc is in the slave thread, the data is alive.

And to your final words: you can't change the Arc. Well, you can, but only by dropping the previous Arc (not the value it its pointing at!) and binding another Arc to the same name. But you can modify the value it is pointing at (through the Mutex), and, since all clones of the Arc point at the same place, the next time they're used to read it they'll all get the change.

It looks like you are unfamiliar with the very concept of pointers and indirection.

Arc is a kind of pointer. You can have multiple pointers indirectly referring to the same location (ie. the same data) in memory. As long as the memory at that location is valid, individual pointers don't care how many other pointers exist or do not exist to the same location, and you can use all of them to access the pointed memory.

Arc is a smart pointer type that allocates some memory once, keeps track of the number of pointers to that one block of memory, and only frees it when no one is looking at it anymore. While there is at least one Arc pointing to the data, you can use it, or any of the other Arc instances, to read the single, shared, pointed object.

Cloning an Arc does not clone the pointed object, it only creates a new pointer pointing to the same location in memory.

So can I manually release the real data in main thread?

Yeah, I've already understood what you've said.I think the biggest problem is that I thought that drop is releasing real data.:joy:

Sure, so to expand on that, dropping something (either explicitly using the drop function, or implicitly by letting it fall out of scope) executes an action specific to the value being dropped. More concretely, its Drop::drop() impl is called. For an Arc, <Arc as Drop>::drop() decrements the reference counter. It does not always immediately drop the pointed data; it only does so once the reference count reaches zero.

Of course not, because otherwise in other threads referring to it you'd get the use-after-free error (and - in the best case - the segmentation fault).

because there are other threads that use this data, so I can't do this for considering the memory safety, but can I do this? I mean, only for ability and mechanism of the language, not considering the memory safe.

You can technically use unsafe, dereference any of the Arc instances, and then manually drop it (using ptr::drop_in_place or ptr::read).

I don't see any valid reason to do it, though. You either manage the memory manually, using unsafe constructs, or you let a safe abstraction handle it for you. Reaching into the guts of an abstraction you didn't write is a recipe for disaster, but you can do pretty much anything you want in unsafe code.

It has been mentioned earlier in this thread, but if you want the data to be dropped when it's dropped in the main thread, then you can give the other thread a Weak<T> instead. A Weak<T> does not keep the data alive by its own, so when the last real Arc<T> gets dropped, the T gets dropped too. Any remaining Weak<T> can not access the data anymore then (it gets an Option::None when trying to).

2 Likes

Thanks you so much, I have fully understood it.

Thanks a lot! I've tried some demo and it's really interesting!