| |
![Jonathan M Davis's Gravatar profile Gravatar of Jonathan M Davis](//www.gravatar.com/avatar/c2372c89dbe85590c9c1dd06c6f998e0?d=identicon&s=32) | Posted by Jonathan M Davis in reply to Andy Valencia | Permalink Reply |
|
Jonathan M Davis ![Jonathan M Davis's Gravatar profile Gravatar of Jonathan M Davis](//www.gravatar.com/avatar/c2372c89dbe85590c9c1dd06c6f998e0?d=identicon&s=80)
Posted in reply to Andy Valencia
| On Sunday, May 26, 2024 8:39:53 AM MDT Andy Valencia via Digitalmars-d-learn wrote:
> The following code fails to compile; it appears from the error message that the library's clear() function is not ready to act on a shared AA?
>
> synchronized class F {
>
> private:
> string[int] mydict;
>
> public:
> void clear() {
> this.mydict.clear();
> }
> }
>
> void
> main()
> {
> auto f = new shared F();
> f.clear();
> }
No operation on an associative array is thread-safe. As such, you should not be doing _any_ operation on a shared AA without first locking a mutex to protect it. Then you need to cast away shared to access or mutate it or do whatever it is you want to do with it other than pass it around. And then when you're done, you make sure that no thread-local references to the AA exist, and you release the mutex. This is true for any type which is not specifically designed to be shared, and none of the built-in types are designed to be shared. They will work as shared so long as you protect them appropriately and then temporarily cast away shared to operate on them as thread-local, but they should never be mutated as shared, and it's on you to make sure that the object is actually protected appropriately when you cast away shared, since the language has no way of knowing when that's actually safe to do. It can just prevent you from accidentally accessing an object when it's shared, since when it's shared, the type system marks it as shared across threads and thus not thread-safe to access.
The language is supposed to prevent you from doing any operations on a shared object which are not guaranteed to be thread-safe (which is pretty much anything other than passing it around by reference or pointer), but unfortunately, since that wasn't entirely implemented up front, some of those checks are currently behind the -preview=nosharedaccess flag and will be made the default at some point in the future (but not now). So, some shared operations may work when they really shouldn't, but the compiler will prevent you from calling a function like clear, because it operates on thread-local variables, not shared ones.
And with regards to AAs, you will need to be particularly careful with the in operator, because it returns a pointer a value in the AA. So, if you cast away shared to operate on it and then use in, you'll get a thread-local pointer to the AA, meaning that the lock needs to stay in place as long as that pointer is around, whereas a function like clear is done as soon as its been called, so unless you're doing more with the AA at that point, you could release the lock then.
Using synchronized is one way to get a mutex, and a synchronized function is implicitly shared, so your clear function is both synchronized and shared, meaning that you can call it on a shared object, and it will lock a mutex when it's called, but you still need to cast away shared from mydict to be able to call anything on it. The compiler is not smart enough to know that removing shared is thread-safe within your function, so it will not do it for you, and the clear function for AAs can't work on shared AAs, because it has no way of guaranteeing that that's thread-safe. So, the solution is that you must explicitly removed shared temporarily to call clear when you know that it's thread-safe to do so.
- Jonathan M Davis
|