|
| 1 | +`Synchronization` module provides common layer for synchronization. It provides same guaranties independent of any particular Ruby implementation. |
| 2 | + |
| 3 | +*This is a new module, it is expected to fully stabilize for 1.0 release.* |
| 4 | + |
| 5 | +## Synchronization::Object |
| 6 | + |
| 7 | +Provides common parent for all objects which need to be synchronized or be using other synchronization tools. It provides: |
| 8 | + |
| 9 | +- Synchronized block |
| 10 | +- Methods for waiting and signaling |
| 11 | +- Volatile fields |
| 12 | +- Ensure visibility of final fields |
| 13 | +- Fields with CAS operations |
| 14 | + |
| 15 | +## Synchronized block |
| 16 | + |
| 17 | +`Synchronization::Object` provides private method `#synchronize(&block)`. For a given object only one Thread can enter one of the blocks synchronized against this object. Object is locked when a thread enters one of the synchronized blocks. |
| 18 | + |
| 19 | +Example of a simple counter which can be used by multiple threads: |
| 20 | + |
| 21 | +```ruby |
| 22 | +class SafeCounter < Concurrent::Synchronization::Object |
| 23 | + def initialize |
| 24 | + super |
| 25 | + synchronize { @count = 0 } |
| 26 | + end |
| 27 | + |
| 28 | + def increment |
| 29 | + synchronize { @count += 1 } |
| 30 | + end |
| 31 | + |
| 32 | + def count |
| 33 | + synchronize { @count } |
| 34 | + end |
| 35 | +end |
| 36 | +``` |
| 37 | + |
| 38 | +### Naming conventions |
| 39 | + |
| 40 | +Methods starting with `ns_` are marking methods that are not using synchronization by themselves, they have to be used inside synchronize block. They are usually used in pairs to separate the synchronization from behavior: |
| 41 | + |
| 42 | +```ruby |
| 43 | +def compute |
| 44 | + service.report synchronize { ns_compute } |
| 45 | +end |
| 46 | + |
| 47 | +private |
| 48 | + |
| 49 | +def ns_compute |
| 50 | + ns_compute_reduce ns_compute_map |
| 51 | +end |
| 52 | +``` |
| 53 | +where `compute` defines how is it synchronized and `ns_compute` handles the behavior (in this case the computation). `ns_` methods should only call other `ns_` methods or `pr_` methods. They can call normal methods on other objects, but that should be done with care (better to avoid) because the thread escapes this object while the lock is still held, which can lead to deadlock. That's why the `report` method is called in `compute` and not in `ns_compute`. |
| 54 | + |
| 55 | +`pr_` methods are pure functions they can be used in and outside of synchronized blocks. |
| 56 | + |
| 57 | +## Methods for waiting and signaling |
| 58 | + |
| 59 | +Sometimes while already inside the synchronized block some condition is not met. Then the thread needs to wait (releasing the lock) until the condition is met. The waiting thread is then signaled that it can continue. |
| 60 | + |
| 61 | +To fulfill these needs there are private methods: |
| 62 | + |
| 63 | +- `ns_wait` {include:Concurrent::Synchronization::AbstractObject#ns_wait} |
| 64 | +- `ns_wait_until` {include:Concurrent::Synchronization::AbstractObject#ns_wait_until} |
| 65 | +- `ns_signal` {include:Concurrent::Synchronization::AbstractObject#ns_signal} |
| 66 | +- `ns_broadcast` {include:Concurrent::Synchronization::AbstractObject#ns_broadcast} |
| 67 | + |
| 68 | +All methods have to be called inside synchronized block. |
| 69 | + |
| 70 | +## Volatile fields |
| 71 | + |
| 72 | +`Synchronization::Object` can have volatile fields (Java semantic). They are defined by `attr_volatile :field_name`. `attr_volatile` defines reader and writer with the `field_name`. Any write is always immediately visible for any subsequent reads of the same field. |
| 73 | + |
| 74 | +## Ensure visibility of final fields |
| 75 | + |
| 76 | +Instance variables assigned only once in `initialize` method are not guaranteed to be visible to all threads. For that user can call `ensure_ivar_visibility!` method, like in following example taken from `Edge::AbstractPromise` implementation: |
| 77 | + |
| 78 | +```ruby |
| 79 | +class AbstractPromise < Synchronization::Object |
| 80 | + def initialize(future, *args, &block) |
| 81 | + super(*args, &block) |
| 82 | + @Future = future |
| 83 | + ensure_ivar_visibility! |
| 84 | + end |
| 85 | + # ... |
| 86 | +end |
| 87 | +``` |
| 88 | + |
| 89 | +### Naming conventions |
| 90 | + |
| 91 | +Instance variables with camel case names are final and never reassigned. |
| 92 | + |
| 93 | +## Fields with CAS operations |
| 94 | + |
| 95 | +They are not supported directly, but AtomicReference can be stored in final field and then CAS operations can be done on it, like in following example taken from `Edge::Event` implementation: |
| 96 | + |
| 97 | +```ruby |
| 98 | +class Event < Synchronization::Object |
| 99 | + extend FutureShortcuts |
| 100 | + |
| 101 | + def initialize(promise, default_executor = :io) |
| 102 | + @Promise = promise |
| 103 | + @DefaultExecutor = default_executor |
| 104 | + @Touched = AtomicBoolean.new(false) |
| 105 | + super() |
| 106 | + ensure_ivar_visibility! |
| 107 | + end |
| 108 | + # ... |
| 109 | + def touch |
| 110 | + # distribute touch to promise only once |
| 111 | + @Promise.touch if @Touched.make_true |
| 112 | + self |
| 113 | + end |
| 114 | + # ... |
| 115 | +end |
| 116 | +``` |
| 117 | + |
| 118 | +## Memory model (sort of) |
| 119 | + |
| 120 | +// TODO |
| 121 | + |
0 commit comments