Re: Looking at reordering memory operations
On Saturday, March 10, 2018 at 1:18:40 PM UTC-8, John Hening wrote: > > Gil, thanks for your response. It is very helpful. > > In your specific example above, there is actually no ordering question, > because your writeTask() operations doesn't actually observe the state > changed by connection.configueBlocking(false) > > > I agree that my question wasn't correct. There is not 'ordering'. I meant > visibility. > Visibility and ordering are related. Questions about the visibility of state (like that of the field "blocking") apply only to things that interact with that state. And when things interact with some state, the ordering in which changes to that state becomes visible (with relation to changes to other state, like e.g. the enqueing of an operation via Executor.execute(), being visible) has (or doesn't have) certain guarantees. E.g. in the example discussed, with the synchronized blocks in place on both the writer and the reader of the field "blocking", we are guaranteed that the change of "blocking = false" is visible to the thread that executes writeTask() (if writeTask actually uses the value of "blocking" obtained within the synchronized block) before the request to execute write() is visible to that same thread... > Without the use of synchronized in isBlocking(), the use of synchronized > in configureBlocking() wouldn't make a difference. > > Yes, semi-synchronized doesn't work. So, I conclude that without a > synchronization the result of `blocking = false` could be invisible for > writeTask, am I right? > It is not a matter of being invisible. It's a field in a shared object, so all operations on it are eventually visible (to things that access it). What you can certainly say here is that without using synchronized blocks *on both ends* (both the writer and the reader), and without being replaced by some other ordering mechanism *on both ends*, your writeTask could observe a value of "blocking" that predates the modification of it in the thread that calls connection.configueBlocking(). > As your question about the possibility of "skipping" some write operations. > > > By skipping I meant 'being invisible for observers'. For example, if one > thread t1 read any not-volatile-integer x then it is possible that t1 see > always the same value of x (though there is another thread t2 that modifies > x). > That (t1 always see the same value of x when x is modified elsewhere) is possible, e.g. in a tight loop reading x and nothing else. But that will only happen if no other ordering constructs force the visibility of modifications to x. E.g if thread t1 read some volatile field y that thread t2 modifies after modifying x, then thread t1 will observe the modified value of x in reads that occur after observing the modified value of y. In such a case, it won't "always see the same value of x". > > > 1. It is interesting for me what about a such situation: > >while(true) { > SocketChannel connection = serverSocketChannel.accept(); > connection.configueBlocking(false); > Unsafe.storeFence(); > executor.execute(() -> writeTask(connection)); > } > void writeTask(SocketChannel s){ > (***) > any_static_global_field = s.isBlocking(); > } > > For my eye it should work but I have doubts. What does it mean storeFence? > Please flush it to the memory immediately! > Unsafe.storeFence doesn't mean "flush...". It means "Ensures lack of reordering of stores before the fence with loads or stores after the fence." (that's literally what the Javadoc for it says). > So, it will be visible before starting the executor thread. But, it seems > that, here, load fence is not necessary (***). Why? The blocking field must > be read from memory (there is no possibility that it is cached, because it > is read the first time by the executor thread). When it comes to CPU cache > it may be cached but cache is coherent = no problem). Moreover, there is no > need to ensure ordering here. So, loadFence is not necessary. Yes? > No. At least not quite. For this specific sequence, you already have the ordering you want, but not for the reasons you think. First, please put aside this notion that there is some memory, and some cache or store buffer, and some flushing going on. This ordering and visibility stuff has nothing to do with any of those potential implementation details. and trying to explain things in terms of those potential (and incomplete) implementation details mostly serves to confuse the issue. A tip I give people for thinking about this stuff is: Always think of the compiler as the culprit when it comes to reordering, and in that thinking, imagine the compiler being super-smart and super-mean. The compiler is allowed to create all sorts of evil, ingenious and pre-cognitive reorderings, cachings, and redundant or dead operation eliminations (including pre-caching of values it thinks you migh
Re: Looking at reordering memory operations
On Saturday, March 10, 2018 at 1:18:40 PM UTC-8, John Hening wrote: > > Gil, thanks for your response. It is very helpful. > > In your specific example above, there is actually no ordering question, > because your writeTask() operations doesn't actually observe the state > changed by connection.configueBlocking(false) > > > I agree that my question wasn't correct. There is not 'ordering'. I meant > visibility. > No real diference between ordering a > > > Without the use of synchronized in isBlocking(), the use of synchronized > in configureBlocking() wouldn't make a difference. > > Yes, semi-synchronized doesn't work. So, I conclude that without a > synchronization the result of `blocking = false` could be invisible for > writeTask, am I right? > > > As your question about the possibility of "skipping" some write operations. > > > By skipping I meant 'being invisible for observers'. For example, if one > thread t1 read any not-volatile-integer x then it is possible that t1 see > always the same value of x (though there is another thread t2 that modifies > x). > > > 1. It is interesting for me what about a such situation: > >while(true) { > SocketChannel connection = serverSocketChannel.accept(); > connection.configueBlocking(false); > Unsafe.storeFence(); > executor.execute(() -> writeTask(connection)); > } > void writeTask(SocketChannel s){ > (***) > any_static_global_field = s.isBlocking(); > } > > For my eye it should work but I have doubts. What does it mean storeFence? > Please flush it to the memory immediately! So, it will be visible before > starting the executor thread. But, it seems that, here, load fence is not > necessary (***). Why? The blocking field must be read from memory (there is > no possibility that it is cached, because it is read the first time by the > executor thread). When it comes to CPU cache it may be cached but cache is > coherent = no problem). Moreover, there is no need to ensure ordering here. > So, loadFence is not necessary. Yes? > > 2. > volatile int foo; > ... > foo = 1; > foo = 2; > foo = 3; > > > > It is very interesting. So, after JITed on x86 it can look like: > > mov &foo, 1 > sfence > mov &foo, 2 > sfence > mov &foo, 3 > sfence > > > > Are you sure that CPU can execute that as: > mov &foo, 3 > sfence > > > ? > > I know that: > > mov &foo, 1 > mov &foo, 2 > mov &foo, 3 > > > > x86-CPU can optimizied it legally. > > > > > > > > > > > > > > > > > W dniu piątek, 9 marca 2018 23:20:37 UTC+1 użytkownik John Hening napisał: >> >> >> executor = Executors.newFixedThreadPool(16); >> while(true) { >> SocketChannel connection = serverSocketChannel.accept(); >> connection.configueBlocking(false); >> executor.execute(() -> writeTask(connection)); >> } >> void writeTask(SocketChannel s){ >> s.isBlocking(); >> } >> >> public final SelectableChannel configureBlocking(boolean block) >> throws IOException >> { >> synchronized (regLock) { >> ... >> blocking = block; >> } >> return this; >> } >> >> >> >> We see the following situation: the main thread is setting >> connection.configueBlocking(false) >> >> and another thread (launched by executor) is reading that. So, it looks >> like a datarace. >> >> My question is: >> >> 1. Here >> configureBlocking >> >> is synchronized so it behaves as memory barrier. It means that code is >> ok- even if reading/writing to >> blocking >> >> field is not synchronized- reading/writing boolean is atomic. >> >> 2. What if >> configureBlocking >> >> wouldn't be synchronized? What in a such situation? I think that it would >> be necessary to emit a memory barrier because it is theoretically possible >> that setting blocking field could be reordered. >> >> Am I right? >> > -- You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group. To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: Looking at reordering memory operations
Gil, thanks for your response. It is very helpful. In your specific example above, there is actually no ordering question, because your writeTask() operations doesn't actually observe the state changed by connection.configueBlocking(false) I agree that my question wasn't correct. There is not 'ordering'. I meant visibility. Without the use of synchronized in isBlocking(), the use of synchronized in configureBlocking() wouldn't make a difference. Yes, semi-synchronized doesn't work. So, I conclude that without a synchronization the result of `blocking = false` could be invisible for writeTask, am I right? As your question about the possibility of "skipping" some write operations. By skipping I meant 'being invisible for observers'. For example, if one thread t1 read any not-volatile-integer x then it is possible that t1 see always the same value of x (though there is another thread t2 that modifies x). 1. It is interesting for me what about a such situation: while(true) { SocketChannel connection = serverSocketChannel.accept(); connection.configueBlocking(false); Unsafe.storeFence(); executor.execute(() -> writeTask(connection)); } void writeTask(SocketChannel s){ (***) any_static_global_field = s.isBlocking(); } For my eye it should work but I have doubts. What does it mean storeFence? Please flush it to the memory immediately! So, it will be visible before starting the executor thread. But, it seems that, here, load fence is not necessary (***). Why? The blocking field must be read from memory (there is no possibility that it is cached, because it is read the first time by the executor thread). When it comes to CPU cache it may be cached but cache is coherent = no problem). Moreover, there is no need to ensure ordering here. So, loadFence is not necessary. Yes? 2. volatile int foo; ... foo = 1; foo = 2; foo = 3; It is very interesting. So, after JITed on x86 it can look like: mov &foo, 1 sfence mov &foo, 2 sfence mov &foo, 3 sfence Are you sure that CPU can execute that as: mov &foo, 3 sfence ? I know that: mov &foo, 1 mov &foo, 2 mov &foo, 3 x86-CPU can optimizied it legally. W dniu piątek, 9 marca 2018 23:20:37 UTC+1 użytkownik John Hening napisał: > > > executor = Executors.newFixedThreadPool(16); > while(true) { > SocketChannel connection = serverSocketChannel.accept(); > connection.configueBlocking(false); > executor.execute(() -> writeTask(connection)); > } > void writeTask(SocketChannel s){ > s.isBlocking(); > } > > public final SelectableChannel configureBlocking(boolean block) throws > IOException > { > synchronized (regLock) { > ... > blocking = block; > } > return this; > } > > > > We see the following situation: the main thread is setting > connection.configueBlocking(false) > > and another thread (launched by executor) is reading that. So, it looks > like a datarace. > > My question is: > > 1. Here > configureBlocking > > is synchronized so it behaves as memory barrier. It means that code is ok- > even if reading/writing to > blocking > > field is not synchronized- reading/writing boolean is atomic. > > 2. What if > configureBlocking > > wouldn't be synchronized? What in a such situation? I think that it would > be necessary to emit a memory barrier because it is theoretically possible > that setting blocking field could be reordered. > > Am I right? > -- You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group. To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: Looking at reordering memory operations
There are many ways in which desired order may be achieved, and you need to examine all code that interacts with the state elements to reason about how (and if) ordering is enforced. Removing the "synchronized" alone (in some seemingly working code) is "always a bad idea". But that doesn't make it "required". The question is, what scheme would you replace it with... In your specific example above, there is actually no ordering question, because your writeTask() operations doesn't actually observe the state changed by connection.configueBlocking(false);. It comes close, but the fact that nothing is done with the return value of isBlocking() means that you have nothing to ask an ordering question about. [the entire execution to isBlocking() is dead code, and will be legitimately eliminated by JIT compilers after inlining]. However, if you change the example slightly such that writeTask() propagated the value of isBlocking() somewhere (e.g. to some static volatile boolean), we'd have a question to deal with., So let's assume you did that... In the specific case of the SocketChannel implementation and the example above, the modification of the SocketChannel-internal blocking state in connection.configueBlocking(false); is guranteed to be visible to the potential observation of the same state by the writeTask() operation run by some executor thread because *both* configureBlocking() and isBlocked() use a synchronized block around the access to the "blocking" field (e.g. at http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/nio/channels/spi/AbstractSelectableChannel.java#AbstractSelectableChannel.isBlocking%28%29). Without the use of synchronized in isBlocking(), the use of synchronized in configureBlocking() wouldn't make a difference. There are many other ways that this ordering guarantee could be achieved. E.g. (for this specific sequence of T1: blocking = false; enqueue writeTask() operation; and T2: start writeTask() operation; writeTask return value of blocking;) making the SocketChannel-internal blocking field volatile would also ensure the writeTask() operation above would return blocking = false. And many other means for ordering these are possible. As your question about the possibility of "skipping" some write operations: Write operations are never actually "skipped" (they will eventually happen). But in situations where a write is followed by a subsequent over-writing write to the same field, the code can legitimately act like it "ran fast enough that no-one was able to observe the intermediate state", and simply execute the last write. The CPU can do this. The Compiler can do this. And the thread running fast enough can do this. It is important to understand that this is true even when synchronization and other ordering operations exist. E.g the following sequence: synchronized(regLock) { blocking = false; } synchronized(regLock) { blocking = true; } synchronized(regLock) { blocking = false; } Can be legitimately executed as: synchronized(regLock) { blocking = false; } And the sequence: volatile int foo; ... foo = 1; foo = 2; foo = 3; can (and will) be legitimately executed as: foo = 3; Ordering questions only come into play if you put other memory-interacting things in the middle, between those writes. Then questions about whether or not those other things can be re-ordered with the writes come up. Sometimes the rules prevent such re-ordering (forcing the actual intermediate writes to be executed), and sometimes the rules allow re-ordering (allowing e..g writes in loops to be pushed to happen only once when the loop completes). In general, in Java, unless some of the "other thing" players are volatile, atomics, or synchronized blocks, any reordering is allowed as long as it does not change the eventual meaning of computations the sequence. On Saturday, March 10, 2018 at 8:13:53 AM UTC-8, John Hening wrote: > > ok, reordering is not a good idea to consider here. But, please note that > if conifgureBlocking wans't synchronized then a statement: > > blocking = block > > could be "skipped" on compilation level because JMM doesn't guarantee you > that every access to the memory will be "commit" to the main memory. > synchronized method/ putting memory barrier would solve that problem. What > do you think? > > > W dniu piątek, 9 marca 2018 23:20:37 UTC+1 użytkownik John Hening napisał: >> >> >> executor = Executors.newFixedThreadPool(16); >> while(true) { >> SocketChannel connection = serverSocketChannel.accept(); >> connection.configueBlocking(false); >> executor.execute(() -> writeTask(connection)); >> } >> void writeTask(SocketChannel s){ >> s.isBlocking(); >> } >> >> public final SelectableChannel configureBlocking(boolean block) >> throws IOException >> { >> synchronized (regLock) { >> ... >> blocking = blo
Re: Looking at reordering memory operations
ok, reordering is not a good idea to consider here. But, please note that if conifgureBlocking wans't synchronized then a statement: blocking = false could be "skipped" on compilation level because JMM doesn't guarantee you that every access to the memory will be "commit" to the main memory. W dniu piątek, 9 marca 2018 23:20:37 UTC+1 użytkownik John Hening napisał: > > > executor = Executors.newFixedThreadPool(16); > while(true) { > SocketChannel connection = serverSocketChannel.accept(); > connection.configueBlocking(false); > executor.execute(() -> writeTask(connection)); > } > void writeTask(SocketChannel s){ > s.isBlocking(); > } > > public final SelectableChannel configureBlocking(boolean block) throws > IOException > { > synchronized (regLock) { > ... > blocking = block; > } > return this; > } > > > > We see the following situation: the main thread is setting > connection.configueBlocking(false) > > and another thread (launched by executor) is reading that. So, it looks > like a datarace. > > My question is: > > 1. Here > configureBlocking > > is synchronized so it behaves as memory barrier. It means that code is ok- > even if reading/writing to > blocking > > field is not synchronized- reading/writing boolean is atomic. > > 2. What if > configureBlocking > > wouldn't be synchronized? What in a such situation? I think that it would > be necessary to emit a memory barrier because it is theoretically possible > that setting blocking field could be reordered. > > Am I right? > -- You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group. To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: Looking at reordering memory operations
On 03/10/2018 12:20 AM, John Hening wrote: | executor =Executors.newFixedThreadPool(16); while(true){ SocketChannelconnection =serverSocketChannel.accept(); connection.configueBlocking(false); executor.execute(()->writeTask(connection)); } voidwriteTask(SocketChannels){ s.isBlocking(); } publicfinalSelectableChannelconfigureBlocking(booleanblock)throwsIOException { synchronized(regLock){ ... blocking =block; } returnthis; } | We see the following situation: the main thread is setting | connection.configueBlocking(false) | and another thread (launched by executor) is reading that. So, it looks like a datarace. My question is: 1. Here | configureBlocking | is synchronized so it behaves as memory barrier. It means that code is ok- even if reading/writing to | blocking | field is not synchronized- reading/writing boolean is atomic. 2. What if | configureBlocking | wouldn't be synchronized? What in a such situation? I think that it would be necessary to emit a memory barrier because it is theoretically possible that setting blocking field could be reordered. Reordered with what? Ordering is always between at least two data accesses. If you figure out what "blocking" is protecting, you'll know whether you need a memory barrier or not. -- You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group. To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.