digitalmars.D - synchronized - shared but actually useful
- FeepingCreature (21/21) Oct 23 2018 1. Make synchronized an attribute of fields, classes and methods
- FeepingCreature (2/2) Oct 23 2018 Addendum: synchronized methods must be pure.
- Stanislav Blinov (5/12) Oct 23 2018 :) What about structs? Or all the cases where you don't need
- FeepingCreature (7/21) Oct 23 2018 That's why I tied it to @safe, so you can always write
- FeepingCreature (15/17) Oct 26 2018 ping
- FeepingCreature (1/1) Oct 30 2018 ping
- Jonathan M Davis (63/80) Oct 30 2018 Really, all shared is about is preventing common bugs related to
- FeepingCreature (15/33) Oct 31 2018 This is simply not correct. People didn't stop having a need for
- Jonathan M Davis (63/95) Oct 31 2018 __gshared is intended for interacting with C globals and that's it. If i...
- FeepingCreature (36/151) Oct 31 2018 This does not match what I'm actually seeing in practice. If this
- Jonathan M Davis (83/139) Oct 31 2018 Probably. There are places in the documentation that talk about it, but ...
- FeepingCreature (19/64) Oct 31 2018 Sorry- I see your point. There is indeed no reason for
- Stanislav Blinov (8/14) Oct 31 2018 And so you're jumping from your "simple oop cases" to "let's
- FeepingCreature (6/20) Oct 31 2018 Sorry, allow me to restate this.
- Jonathan M Davis (19/24) Oct 31 2018 synchronized classes are only partially implemented, and the key feature
- FeepingCreature (5/11) Oct 31 2018 You could pass other synchronized objects, immutable data and
- FeepingCreature (13/25) Jul 01 2022 Apropos of running into a production threading error that would
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (2/13) Jul 01 2022 That is a weird bug, it works for ‘e=e+1’?
- FeepingCreature (6/20) Jul 01 2022 Huh... cool!
- FeepingCreature (15/15) Oct 31 2018 Update to previous post:
1. Make synchronized an attribute of fields, classes and methods 2. synchronized methods are synchronized(this) wrapping inconditions, outconditions, invariant checks and the main body. They are thus actually safe, as opposed to the status quo where invariants have to be synchronized separately and are thus Worse Than Useless™. 3. synchronized classes consist of synchronized fields and synchronized public methods 3.1. Only synchronized subclasses may inherit from synchronized classes, and vice versa. 4. immutable data is implicitly synchronized. Local variables are implicitly synchronized. 5. Synchronized methods must not allow mutable references to escape, unless they are to synchronized objects. 6. in safe, only synchronized methods may access synchronized fields. Advantages: * simple * does something useful Disadvantages: * inability to generate giant forum debates (fingers crossed)
Oct 23 2018
Addendum: synchronized methods must be pure. No setting a global variable from two different threads for you!
Oct 23 2018
On Tuesday, 23 October 2018 at 08:56:39 UTC, FeepingCreature wrote:1. Make synchronized an attribute of fields, classes and methods ... Advantages: * simple * does something useful Disadvantages: * inability to generate giant forum debates (fingers crossed):) What about structs? Or all the cases where you don't need 'synchronized'? You can write a lock-free queue with a few integers and an array.
Oct 23 2018
On Tuesday, 23 October 2018 at 12:50:30 UTC, Stanislav Blinov wrote:On Tuesday, 23 October 2018 at 08:56:39 UTC, FeepingCreature wrote:That's why I tied it to safe, so you can always write synchronized trusted code that needs manual thread safety. That said, clever threading solutions, atomics and the like are mostly out of scope for this proposal, which is primarily aimed at simple oop-based code.1. Make synchronized an attribute of fields, classes and methods ... Advantages: * simple * does something useful Disadvantages: * inability to generate giant forum debates (fingers crossed):) What about structs? Or all the cases where you don't need 'synchronized'? You can write a lock-free queue with a few integers and an array.
Oct 23 2018
ping On Tuesday, 23 October 2018 at 08:56:39 UTC, FeepingCreature wrote:Disadvantages: * inability to generate giant forum debates (fingers crossed)I consider the point proven, but the downside of being unable to generate giant flamewars is that you eventually drop off the frontpage. I really think the shared viewpoint on thread-owned data and how to move data from thread to thread is very alien to how thread safety is handled in practice. In practice, a lot of data is shared; I would argue a large fraction of *all* mutable data is shared, and access is protected using synchronization in the owning class. That is, objects, not threads, are the owners and overseers of mutable data. Whatever solution for thread safety we arrive at should account for this.
Oct 26 2018
On Friday, October 26, 2018 7:21:50 AM MDT FeepingCreature via Digitalmars-d wrote:ping On Tuesday, 23 October 2018 at 08:56:39 UTC, FeepingCreature wrote:Really, all shared is about is preventing common bugs related to multithreading by segregating data shared across threads and making operations which aren't guaranteed to be thread-safe illegal (the main benefit then being that you know that everything else then isn't shared across threads). The language doesn't do that entirely correctly at the moment (e.g. copying and assigning shared data is currently legal in spite of not being thread-safe) but ultimately, when you use shared, what you're doing is pretty much exactly what you'd do in C/C++ except that the compiler prevents many bugs with regards to data shared across threads, and because of the extra type information in place to enable the restrictions to give you that protection, you're often forced to cast away shared in trusted code to then do what you would have done in C++. But all of the atomics and mutexes and whatnot are the same as what you'd be doing in C++. The basic design patterns are the same. It's the fact that you you're getting compiler errors when you do something that's not thread-safe that's different, and it's the fact that you have to cast away shared after locking a mutex that's different (since C++ doesn't have shared). But aside from that, what you're doing is the same. The fact that the compiler prevents you from doing basic stuff while the variable is shared and requires casting (unless you use atomics) frequently causes folks to get mad at shared and misunderstand it, but that's really the compiler preventing you from shooting yourself in the foot. Once you understand what's going on, it's really just the same as C++ but with some extra casts. So, shared is very low level ultimately. It's about how the data is stored and accessed, not about how higher level constructs are written. We can then build other, higher level mechanisms on top of that - such as std.concurrency - but at the language level, whether you're passing data across threads or having multiple threads access data on the same thread isn't really part of the design. The language is just concerned about the fact that the data is shared. The only higher level threading mechanism that the language has is synchronized, and all that really is is a built-in mutex, which in conjunction whith synchronized classes (if they're ever fully implemented) would allow for the implicit casting away of the top level of shared inside of member functions, but that's really as far as it goes, and without some kind of ownership model in the language, it's _very_ hard to go much farther than that - either with implicitly removing shared in code or with having passing ownership across threads work without explicit casts. Certainly, I think that any attempt to move shared away from being a low level construct indicating simply that the data is shared across threads is mis-guided. It's what other constructs for sharing data across threads are built upon. So, if someone wants to push for some new mechanism that improves sharing data across threads (rather than passing it across threads) that's built on top of shared, that's fine. If someone wants to push for some new mechanism that improves sharing data by passing it across threads, that's fine. But shared is a low level construct and should stay that way. It's the base for everything else. And to be honest, I don't really expect it to be reasonable to add much in the way of higher level mechanisms for sharing data to D at the language level - not with it being a systems language. Most stuff would require restricting it further and/or introducing a serious ownership model to it, which we really don't want to do. Even synchronized classes are pretty questionable, because they can only safely remove the outer layer of shared, which is arguably better than nothing, but it doesn't get you very far. Without a full-blown ownership model, we're pretty much reduced to manual casting when mutexes or synchronized are used, and the language really isn't going to be much help. If someone can come up with some bright ideas, great. All the more power to them. Maybe we can improve the situation. But again, they should be built on top of shared as a low level mechanism, not try to change what shared is. - Jonathan M DavisDisadvantages: * inability to generate giant forum debates (fingers crossed)I consider the point proven, but the downside of being unable to generate giant flamewars is that you eventually drop off the frontpage. I really think the shared viewpoint on thread-owned data and how to move data from thread to thread is very alien to how thread safety is handled in practice. In practice, a lot of data is shared; I would argue a large fraction of *all* mutable data is shared, and access is protected using synchronization in the owning class. That is, objects, not threads, are the owners and overseers of mutable data. Whatever solution for thread safety we arrive at should account for this.
Oct 30 2018
On Tuesday, 30 October 2018 at 18:38:48 UTC, Jonathan M Davis wrote:But shared is a low level construct and should stay that way. It's the base for everything else.This is simply not correct. People didn't stop having a need for threading while D was figuring out how to unbreak shared for ten years and counting. Fact is, the base for everything else is __gshared and synchronized, and it will *continue* to be __gshared and synchronized unless __gshared is removed and the Thread constructor is changed to only take shared delegates. Why not do something useful with synchronized instead?And to be honest, I don't really expect it to be reasonable to add much in the way of higher level mechanisms for sharing data to D at the language level - not with it being a systems language. Most stuff would require restricting it further and/or introducing a serious ownership model to it, which we really don't want to do. Even synchronized classes are pretty questionable, because they can only safely remove the outer layer of shared, which is arguably better than nothing, but it doesn't get you very far. Without a full-blown ownership model, we're pretty much reduced to manual casting when mutexes or synchronized are used, and the language really isn't going to be much help. If someone can come up with some bright ideas, great. All the more power to them. Maybe we can improve the situation. But again, they should be built on top of shared as a low level mechanism, not try to change what shared is. - Jonathan M DavisSorry, but can you please actually comment on my proposal in concrete terms rather than handwave about how we're reduced to manual casting (which is not necessary for concurrency at the moment, so I'm not sure why you think it's unavoidable), and how the language surely cannot be of much help without even commenting on a proposal for how it *could* actually be of help?
Oct 31 2018
On Wednesday, October 31, 2018 1:00:32 AM MDT FeepingCreature via Digitalmars-d wrote:On Tuesday, 30 October 2018 at 18:38:48 UTC, Jonathan M Davis wrote:__gshared is intended for interacting with C globals and that's it. If it's used for anything else, it's not being used for its intended purpose. Certainly, using it for D objects is just begging for bugs. __gshared should _never_ be used for D objects. I know that some folks do, and sometimes folks get away with it, but it's just asking for trouble, and there's no guarantee that such code will continue to work, because it's violating compiler guarantees when it does so. __gshared is designed solely as a backdoor for dealing with C and that is it. On the whole, shared really is _not_ broken and _does_ work. Yes, it has some rough edges that need fixing, and not everything in core.sync has been updated to use it properly, necessitating more casts than should be necessary when using those constructs than should be necessary, but shared as a whole is sound and always has been. The biggest problem with it is that most folks don't understand how to use it properly.But shared is a low level construct and should stay that way. It's the base for everything else.This is simply not correct. People didn't stop having a need for threading while D was figuring out how to unbreak shared for ten years and counting. Fact is, the base for everything else is __gshared and synchronized, and it will *continue* to be __gshared and synchronized unless __gshared is removed and the Thread constructor is changed to only take shared delegates. Why not do something useful with synchronized instead?Manual casting is required if you're actually using shared with mutexes or synchronized. If you're using __gshared, it's not, but that's only because you're lying to the compiler about whether the variables are shared are not, which means that you're circumventing the type system, and you're risking subtle bugs in your programs, because the guarantees that the type system is suppose make aren't actually in place. Your synchronized field proposal is basically just a variation of synchronized classes with the extra caveat that somehow mutable references to the member variables can't escape, and for some reason, not everything in the class has to be synchronized. It's like you're trying to have synchronized classes without talking about shared. But you can't take shared out of the mix. Per the type system, anything that isn't shared is considered thread-local by the compiler, and trying to do anything like this without shared would be a serious violation of the type system and the compiler's guarantees. Instead of talking about safely casting away the outer layer of shared inside the class, you're talking about preventing escaping. synchronized classes as described in TDPL already guaranteed that the outer layer can't escape, since it lives directly in the class. What they can't guarantee is that the rest can't escape, and the language doesn't provide any way for such a guarantee. scope (even with DIP 1000) isn't transitive, so it doesn't really do the trick. So, I don't know how we could guarantee that _nothing_ escapes, though if nothing escapes, then all levels of shared could be implicitly cast away instead of just the outer layer, which would be nice. Also, even if we _could_ somehow prevent anything from escaping, I would point out that mutability has nothing to do with this (or at least constness doesn't), because reading has many of the same threading issues as writing. In order to avoid threading issues, the data must actually be immutable. So, allowing const references to escape would still violate thread-safety. Access to data must be synchronized unless it is immutable - whether we're talking about reading or writing is irrelevant. synchronized classes work (or would work if fully implemented) by guaranteeing that a single level can't escape - the one level that it _can_ guarantee can't escape - and thus are able to properly synchronize that level. If scope were transitive, then maybe we could do better, but since it isn't (and Walter insists that it can't reasonbly be transitive), I'm not sure that it's really reasonable to prevent escaping transitively. In any case, your proposal is just a variation of synchronized classes. So, while it may state it in a new way, the basic concept really isn't anything new. And my point still stands that anything that we do with regards to thread safety must be built on top of shared, because that is the building block of sharing data across threads in D. Whether you like the specifics of shared or not, the idea that the compiler can rely on the assumption that data is _not_ shared across threads unless it's marked as such is a key feature of D. And attempting to use __gshared to get around it is just shooting yourself in the foot. - Jonathan M DavisAnd to be honest, I don't really expect it to be reasonable to add much in the way of higher level mechanisms for sharing data to D at the language level - not with it being a systems language. Most stuff would require restricting it further and/or introducing a serious ownership model to it, which we really don't want to do. Even synchronized classes are pretty questionable, because they can only safely remove the outer layer of shared, which is arguably better than nothing, but it doesn't get you very far. Without a full-blown ownership model, we're pretty much reduced to manual casting when mutexes or synchronized are used, and the language really isn't going to be much help. If someone can come up with some bright ideas, great. All the more power to them. Maybe we can improve the situation. But again, they should be built on top of shared as a low level mechanism, not try to change what shared is.Sorry, but can you please actually comment on my proposal in concrete terms rather than handwave about how we're reduced to manual casting (which is not necessary for concurrency at the moment, so I'm not sure why you think it's unavoidable), and how the language surely cannot be of much help without even commenting on a proposal for how it *could* actually be of help?
Oct 31 2018
On Wednesday, 31 October 2018 at 07:57:59 UTC, Jonathan M Davis wrote:On Wednesday, October 31, 2018 1:00:32 AM MDT FeepingCreature via Digitalmars-d wrote:This does not match what I'm actually seeing in practice. If this is how it is, then this admonishment should be written in the D style guide in big, bolded letters. Generally we try to write cast-free code if possible, so casting away shared feels wrong. That's not really an argument, of course, but to me semantic casting indicates "no compiler, you are mistaken about the properties of this data." The compiler should never *require* casting.On Tuesday, 30 October 2018 at 18:38:48 UTC, Jonathan M Davis wrote:__gshared is intended for interacting with C globals and that's it. If it's used for anything else, it's not being used for its intended purpose. Certainly, using it for D objects is just begging for bugs. __gshared should _never_ be used for D objects. I know that some folks do, and sometimes folks get away with it, but it's just asking for trouble, and there's no guarantee that such code will continue to work, because it's violating compiler guarantees when it does so. __gshared is designed solely as a backdoor for dealing with C and that is it. On the whole, shared really is _not_ broken and _does_ work. Yes, it has some rough edges that need fixing, and not everything in core.sync has been updated to use it properly, necessitating more casts than should be necessary when using those constructs than should be necessary, but shared as a whole is sound and always has been. The biggest problem with it is that most folks don't understand how to use it properly.But shared is a low level construct and should stay that way. It's the base for everything else.This is simply not correct. People didn't stop having a need for threading while D was figuring out how to unbreak shared for ten years and counting. Fact is, the base for everything else is __gshared and synchronized, and it will *continue* to be __gshared and synchronized unless __gshared is removed and the Thread constructor is changed to only take shared delegates. Why not do something useful with synchronized instead?Manual casting is required if you're actually using shared with mutexes or synchronized. If you're using __gshared, it's not, but that's only because you're lying to the compiler about whether the variables are shared are not, which means that you're circumventing the type system, and you're risking subtle bugs in your programs, because the guarantees that the type system is suppose make aren't actually in place.And to be honest, I don't really expect it to be reasonable to add much in the way of higher level mechanisms for sharing data to D at the language level - not with it being a systems language. Most stuff would require restricting it further and/or introducing a serious ownership model to it, which we really don't want to do. Even synchronized classes are pretty questionable, because they can only safely remove the outer layer of shared, which is arguably better than nothing, but it doesn't get you very far. Without a full-blown ownership model, we're pretty much reduced to manual casting when mutexes or synchronized are used, and the language really isn't going to be much help. If someone can come up with some bright ideas, great. All the more power to them. Maybe we can improve the situation. But again, they should be built on top of shared as a low level mechanism, not try to change what shared is.Sorry, but can you please actually comment on my proposal in concrete terms rather than handwave about how we're reduced to manual casting (which is not necessary for concurrency at the moment, so I'm not sure why you think it's unavoidable), and how the language surely cannot be of much help without even commenting on a proposal for how it *could* actually be of help?Your synchronized field proposal is basically just a variation of synchronized classes with the extra caveat that somehow mutable references to the member variables can't escape, and for some reason, not everything in the class has to be synchronized.Everything in the class has to be synchronized. I'm not sure where I said differently.It's like you're trying to have synchronized classes without talking about shared. But you can't take shared out of the mix. Per the type system, anything that isn't shared is considered thread-local by the compiler, and trying to do anything like this without shared would be a serious violation of the type system and the compiler's guarantees.Again, imo this belongs in either the spec or the DStyle guide in big bold letters.Instead of talking about safely casting away the outer layer of shared inside the class, you're talking about preventing escaping. synchronized classes as described in TDPL already guaranteed that the outer layer can't escape, since it lives directly in the class. What they can't guarantee is that the rest can't escape, and the language doesn't provide any way for such a guarantee.Not sure what you mean by "outer layer" here or this entire paragraph.scope (even with DIP 1000) isn't transitive, so it doesn't really do the trick. So, I don't know how we could guarantee that _nothing_ escapes, though if nothing escapes, then all levels of shared could be implicitly cast away instead of just the outer layer, which would be nice. Also, even if we _could_ somehow prevent anything from escaping, I would point out that mutability has nothing to do with this (or at least constness doesn't), because reading has many of the same threading issues as writing. In order to avoid threading issues, the data must actually be immutable. So, allowing const references to escape would still violate thread-safety. Access to data must be synchronized unless it is immutable - whether we're talking about reading or writing is irrelevant.Indeed, const is useless for threading, which is why my proposal explicitly only notes immutable data as implicitly synchronized. We're on the same page.synchronized classes work (or would work if fully implemented) by guaranteeing that a single level can't escape - the one level that it _can_ guarantee can't escape - and thus are able to properly synchronize that level. If scope were transitive, then maybe we could do better, but since it isn't (and Walter insists that it can't reasonbly be transitive), I'm not sure that it's really reasonable to prevent escaping transitively.The point of the proposal is not precisely to prevent escaping, but to prevent the escape of *data structures/references that offer the ability to mutate data unprotected.* Remember that in this model, the object is the unit of data ownership. So the two things that are allowed to escape are immutable data (and non-reference copied data), and synchronized classes, because they can guarantee that they're taking care of protecting their data.In any case, your proposal is just a variation of synchronized classes. So, while it may state it in a new way, the basic concept really isn't anything new.I didn't say it was! I said it was *useful* and *straightforward*.And my point still stands that anything that we do with regards to thread safety must be built on top of shared, because that is the building block of sharing data across threads in D. Whether you like the specifics of shared or not, the idea that the compiler can rely on the assumption that data is _not_ shared across threads unless it's marked as such is a key feature of D. And attempting to use __gshared to get around it is just shooting yourself in the foot. - Jonathan M DavisAnd yet, there's no way to even *state* that the Thread constructor must not take a delegate that can access unshared data, and none of the current proposals list one. Should we demand that only shared data can be passed to new threads? That's going to limit their usefulness. With synchronized, we can deprecate the delegate constructor, then pass the thread data as synchronized objects to a pure function, thereby actually guaranteeing thread safety.
Oct 31 2018
On Wednesday, October 31, 2018 2:08:58 AM MDT FeepingCreature via Digitalmars-d wrote:On Wednesday, 31 October 2018 at 07:57:59 UTC, Jonathan M DavisProbably. There are places in the documentation that talk about it, but it's almost certainly not clear enough given how many folks keep using it. Certainly, far too many folks run to __gshared when they get annoyed with shared. In general, we've done a terrible job messaging how shared works, in part because it's been 95% complete for ages, and it's never been a priority. In general, what shared must do (and _mostly_ does) is guarantee that no operations that are not thread-safe are illegal. Earlier on, there was a larger push to find ways to get the compiler to then do stuff for you to make stuff thread-safe (such as introducing write barriers), and Andrei tends to prefer that approach, but that approach is hard to do (if possible at all), and pieces that we _can_ do (like write barriers) tend to be inefficient. Walter prefers the approach of just making more operations illegal (which unfortunately, then tends to require more casts and more trusted code). So, some stuff has been left in limbo that really shouldn't have been left in limbo, and so we have shared - which works - but where some operations aren't actually thread-safe but are still legal (hence why I say it's 95% complete). And that still needs to be sorted out. And since it hasn't been a priority, and it's perpetually mostly finished, you don't tend to get much in the way of stuff like documentation write-ups about it. That really should be fixed.Manual casting is required if you're actually using shared with mutexes or synchronized. If you're using __gshared, it's not, but that's only because you're lying to the compiler about whether the variables are shared are not, which means that you're circumventing the type system, and you're risking subtle bugs in your programs, because the guarantees that the type system is suppose make aren't actually in place.This does not match what I'm actually seeing in practice. If this is how it is, then this admonishment should be written in the D style guide in big, bolded letters.Generally we try to write cast-free code if possible, so casting away shared feels wrong. That's not really an argument, of course, but to me semantic casting indicates "no compiler, you are mistaken about the properties of this data." The compiler should never *require* casting.In the case of shared, it does require casting, and there really isn't a good way around it. The two major exceptions are atomics and synchronized classes (though those aren't fully implemented). synchronized classes still do the casting; they're just able to cast away shared on the outer layer implicitly. It would be fantastic to be able to do it implicitly in more cases, but it can only be done implicitly in cases where the compiler can actually guarantee that it's thread-safe, and that's really, really hard to do. synchronized classes are the best proposal that we've had for a mechanism for doing so. More would likely be possible with a more complicated ownership model, but without that, the compiler simply doesn't have enough information. It needs to know stuff like that a mutex is associated with a particular variable or set of variables, that it's definitely locked in a section of code, and that it's absolutely impossible that there are any other references to that data which are not currently protected by that mutex. The biggest obstacle there is probably the bit about there being no references not currently protected. Without a real ownership model, that just doesn't work. Maybe someone will come up with a bright idea, but everything I've seen at best has subtle holes in it, and ultimately, it comes down to basically writing the same kind of code that you'd write in C/C++ except that you need a few extra casts within the sections of code where the shared variables are protected by a locked mutex.You were talking about marking individual fields and variables as synchronized rather than simply marking the entire class as synchronized.Your synchronized field proposal is basically just a variation of synchronized classes with the extra caveat that somehow mutable references to the member variables can't escape, and for some reason, not everything in the class has to be synchronized.Everything in the class has to be synchronized. I'm not sure where I said differently.The part directly embedded in the class and which therefore cannot possibly escape. Unlike regular classes, synchronized classes do not allow direct access to their member variables - even from anything else in the same module - in order to be able to provide that guarantee. If you don't have your own copy of TDPL, I'd suggest reading the concurrency chapter which is available for free online, since it talks about synchronized classes: http://www.informit.com/articles/article.aspx?p=1609144 Unfortunately, they're only partially implemented. IIRC, restricting access to the member variables has been implemented, but beyond that, right now they're mostly just synchronized functions like in Java.It's like you're trying to have synchronized classes without talking about shared. But you can't take shared out of the mix. Per the type system, anything that isn't shared is considered thread-local by the compiler, and trying to do anything like this without shared would be a serious violation of the type system and the compiler's guarantees.Again, imo this belongs in either the spec or the DStyle guide in big bold letters.Instead of talking about safely casting away the outer layer of shared inside the class, you're talking about preventing escaping. synchronized classes as described in TDPL already guaranteed that the outer layer can't escape, since it lives directly in the class. What they can't guarantee is that the rest can't escape, and the language doesn't provide any way for such a guarantee.Not sure what you mean by "outer layer" here or this entire paragraph.Well, in principle if you're passing data across threads, it should be shared while it's passed across threads, and the type system requires that. That's why you have to cast to immutable when passing reference types with std.concurrency (shared would work too except for an issue with std.variant's implementation). You can cast to thread-local again on the other side, but to get it across, it needs to be typed as shared / immutable, because that's what it really is at that point. And what you're doing is inherently system, hence why the casting is required. It's up to the programmer to ensure that no references to the data remain on the original thread. If we had a full-own ownership model in the type system, then we might be able to avoid that, but we don't so we can't. Thread is a bit different, but the concept is basically the same. Conceptually, it's being passed off to another thread, so it really should be shared or immutable, but C doesn't actually _have_ shared (hence __gshared), so everything involved _has_ to be trusted internally. Looking at Thread's constructor, having it take a delegate probably isn't a problem, but what _is_ a problem is the fact that it's safe. I don't know how we can reasonably fix that without breaking code, but it's definitely a problem. All data passed through that delegate must either be shared or it must be the only reference to that data so that when it ends up on that new thread, and it's thread-local, no other thread has reference to it, and it really is properly thread-local. That constructor can't guarantee that. It's up to the programmer to guarantee that. So, it needs to be system. If we could somehow require that the delegate only accepted shared data, then we could reasonably make the constructor safe, but since AFAIK, that's not possible, it needs to be system. - Jonathan M DavisAnd my point still stands that anything that we do with regards to thread safety must be built on top of shared, because that is the building block of sharing data across threads in D. Whether you like the specifics of shared or not, the idea that the compiler can rely on the assumption that data is _not_ shared across threads unless it's marked as such is a key feature of D. And attempting to use __gshared to get around it is just shooting yourself in the foot.And yet, there's no way to even *state* that the Thread constructor must not take a delegate that can access unshared data, and none of the current proposals list one. Should we demand that only shared data can be passed to new threads? That's going to limit their usefulness. With synchronized, we can deprecate the delegate constructor, then pass the thread data as synchronized objects to a pure function, thereby actually guaranteeing thread safety.
Oct 31 2018
On Wednesday, 31 October 2018 at 09:04:43 UTC, Jonathan M Davis wrote:Sorry- I see your point. There is indeed no reason for non-synchronized fields. Strike that from the proposal. I'm not sure why I had it in there.Everything in the class has to be synchronized. I'm not sure where I said differently.You were talking about marking individual fields and variables as synchronized rather than simply marking the entire class as synchronized.The part directly embedded in the class and which therefore cannot possibly escape. Unlike regular classes, synchronized classes do not allow direct access to their member variables - even from anything else in the same module - in order to be able to provide that guarantee. If you don't have your own copy of TDPL, I'd suggest reading the concurrency chapter which is available for free online, since it talks about synchronized classes: http://www.informit.com/articles/article.aspx?p=1609144Wow, I straight up hadn't known that synchronized classes were a thing. I think it's because synchronized is not listed as an attribute on the Attributes page, but rather as a separate syntax on the Class page.There doesn't seem to be a trait to detect a synchronized class. Having learnt that synchronized classes are actually mostly implemented (how did I not know this! Though I still think the fact that they can be impure is an issue, but that's statically checkable at least), it seems like it should at least be possible to require that the Thread runner only accesses shared data, by limiting it to take a pure function that only has synchronized-class parameters. Well, it would be, if there was a trait for synchronized classes, but that seems like it'd be much easier to add.And yet, there's no way to even *state* that the Thread constructor must not take a delegate that can access unshared data, and none of the current proposals list one. Should we demand that only shared data can be passed to new threads? That's going to limit their usefulness. With synchronized, we can deprecate the delegate constructor, then pass the thread data as synchronized objects to a pure function, thereby actually guaranteeing thread safety.[ snip std.concurrency which we're not using ] Thread is a bit different, but the concept is basically the same. Conceptually, it's being passed off to another thread, so it really should be shared or immutable, but C doesn't actually _have_ shared (hence __gshared), so everything involved _has_ to be trusted internally. Looking at Thread's constructor, having it take a delegate probably isn't a problem, but what _is_ a problem is the fact that it's safe. I don't know how we can reasonably fix that without breaking code, but it's definitely a problem. All data passed through that delegate must either be shared or it must be the only reference to that data so that when it ends up on that new thread, and it's thread-local, no other thread has reference to it, and it really is properly thread-local. That constructor can't guarantee that. It's up to the programmer to guarantee that. So, it needs to be system. If we could somehow require that the delegate only accepted shared data, then we could reasonably make the constructor safe, but since AFAIK, that's not possible, it needs to be system. - Jonathan M Davis
Oct 31 2018
On Wednesday, 31 October 2018 at 09:36:22 UTC, FeepingCreature wrote:it seems like it should at least be possible to require that the Thread runner only accesses shared data, by limiting it to take a pure function that only has synchronized-class parameters. Well, it would be, if there was a trait for synchronized classes, but that seems like it'd be much easier to add.And so you're jumping from your "simple oop cases" to "let's infect the runtime with a narrow-minded view of thread-safety and inflict synchronization on everyone using threads"? And what if anyone, deity forbid, wants to pass data that is not actually mutex-protected, because they happen to know that there's a lot more to synchronizing data access than that?
Oct 31 2018
On Wednesday, 31 October 2018 at 09:41:58 UTC, Stanislav Blinov wrote:On Wednesday, 31 October 2018 at 09:36:22 UTC, FeepingCreature wrote:Sorry, allow me to restate this. It seems like it should be possible that we, in our utility libraries, write a Thread wrapper that actually enforces that <see above>.it seems like it should at least be possible to require that the Thread runner only accesses shared data, by limiting it to take a pure function that only has synchronized-class parameters. Well, it would be, if there was a trait for synchronized classes, but that seems like it'd be much easier to add.And so you're jumping from your "simple oop cases" to "let's infect the runtime with a narrow-minded view of thread-safety and inflict synchronization on everyone using threads"? And what if anyone, deity forbid, wants to pass data that is not actually mutex-protected, because they happen to know that there's a lot more to synchronizing data access than that?
Oct 31 2018
On Wednesday, October 31, 2018 3:36:22 AM MDT FeepingCreature via Digitalmars-d wrote:There doesn't seem to be a trait to detect a synchronized class. Having learnt that synchronized classes are actually mostly implemented (how did I not know this! Though I still think the fact that they can be impure is an issue, but that's statically checkable at least),synchronized classes are only partially implemented, and the key feature that they implicitly remove the outer layer of shared in their member functions is one of the things that isn't implemented. I don't know exactly what is and isn't implemented at the moment. I recall restrictions on accessing member variables from outside the class being implemented, but I don't think that anyone has really sat down and worked through what's left to do (though I could be wrong - I don't follow the list of dmd PR's closely). I don't see why the member functions being impure is a problem. Direct access to the member variables is protected, and I don't think that you're supposed to be allowed to pass a pointer to a member variable out of the class, but you can escape stuff that the member variables refer to, so I don't see why it matters particularly that they would have access to module-level variables. synchronized classes really only protect the stuff that sit directly inside the class itself, not what the class' member variables refer to. If they were to fully protect everything inside of it, then you couldn't really pass anything in or out of it. - Jonathan M Davis
Oct 31 2018
On Wednesday, 31 October 2018 at 10:25:52 UTC, Jonathan M Davis wrote:synchronized classes really only protect the stuff that sit directly inside the class itself, not what the class' member variables refer to. If they were to fully protect everything inside of it, then you couldn't really pass anything in or out of it. - Jonathan M DavisYou could pass other synchronized objects, immutable data and data by value. In many project designs, that's really all you need.
Oct 31 2018
On Wednesday, 31 October 2018 at 10:36:35 UTC, FeepingCreature wrote:On Wednesday, 31 October 2018 at 10:25:52 UTC, Jonathan M Davis wrote:Apropos of running into a production threading error that would have been fixed by this synchronized proposal, let me just bump this thread to note that it's four years later and this code still fails to compile for absolutely zero reason: ``` synchronized class A { private int e; void foo() { e++; } } ```synchronized classes really only protect the stuff that sit directly inside the class itself, not what the class' member variables refer to. If they were to fully protect everything inside of it, then you couldn't really pass anything in or out of it. - Jonathan M DavisYou could pass other synchronized objects, immutable data and data by value. In many project designs, that's really all you need.
Jul 01 2022
On Friday, 1 July 2022 at 07:13:23 UTC, FeepingCreature wrote:Apropos of running into a production threading error that would have been fixed by this synchronized proposal, let me just bump this thread to note that it's four years later and this code still fails to compile for absolutely zero reason: ``` synchronized class A { private int e; void foo() { e++; } } ```That is a weird bug, it works for ‘e=e+1’?
Jul 01 2022
On Friday, 1 July 2022 at 07:42:07 UTC, Ola Fosheim Grøstad wrote:On Friday, 1 July 2022 at 07:13:23 UTC, FeepingCreature wrote:Huh... cool! I've ran into some issues with trying it in a somewhat nontrivial class (it doesn't really interact with associative arrays correctly), but if this *should* work now, I'm going to try it on simpler classes. Maybe it's good now for some cases!Apropos of running into a production threading error that would have been fixed by this synchronized proposal, let me just bump this thread to note that it's four years later and this code still fails to compile for absolutely zero reason: ``` synchronized class A { private int e; void foo() { e++; } } ```That is a weird bug, it works for ‘e=e+1’?
Jul 01 2022
Update to previous post: synchronized class Class { void foo() safe { } } void main() { auto object = new Class; object.foo(); } This errors because object is not shared. Why is that a problem? It's not shared, but it is synchronized; there is no possibility of a threading issue here. Analogously, I cannot do synchronized class Class { private Class object; Class foo() safe { return this.object; } } either, despite the fact that Class is again synchronized and thus should be able to be treated as shared implicitly.
Oct 31 2018