diff --git a/README.md b/README.md index 74b893001..03a1fda1b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Collection classes that were originally part of the (deprecated) `thread_safe` g * [Array](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Array.html) A thread-safe subclass of Ruby's standard [Array](http://ruby-doc.org/core-2.2.0/Array.html). * [Hash](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Hash.html) A thread-safe subclass of Ruby's standard [Hash](http://ruby-doc.org/core-2.2.0/Hash.html). +* [Set](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Set.html) A thread-safe subclass of Ruby's standard [Set](http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html). * [Map](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Map.html) A hash-like object that should have much better performance characteristics, especially under high concurrency, than `Concurrent::Hash`. * [Tuple](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Tuple.html) A fixed size array with volatile (synchronized, thread safe) getters/setters. diff --git a/lib/concurrent.rb b/lib/concurrent.rb index 1ea1f154e..d7377b54d 100644 --- a/lib/concurrent.rb +++ b/lib/concurrent.rb @@ -12,6 +12,7 @@ require 'concurrent/atom' require 'concurrent/array' require 'concurrent/hash' +require 'concurrent/set' require 'concurrent/map' require 'concurrent/tuple' require 'concurrent/async' @@ -126,5 +127,4 @@ # * Exclude features that don't make sense in Ruby # * Be small, lean, and loosely coupled module Concurrent - -end +end \ No newline at end of file diff --git a/lib/concurrent/set.rb b/lib/concurrent/set.rb new file mode 100644 index 000000000..ea6e85f09 --- /dev/null +++ b/lib/concurrent/set.rb @@ -0,0 +1,47 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' +require 'set' + +module Concurrent + if Concurrent.on_cruby? + + # Because MRI never runs code in parallel, the existing + # non-thread-safe structures should usually work fine. + + # @!macro [attach] concurrent_Set + # + # A thread-safe subclass of Set. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`. + # + # @note `a += b` is **not** a **thread-safe** operation on + # `Concurrent::Set`. It reads Set `a`, then it creates new `Concurrent::Set` + # which is union of `a` and `b`, then it writes the union to `a`. + # The read and write are independent operations they do not form a single atomic + # operation therefore when two `+=` operations are executed concurrently updates + # may be lost. Use `#merge` instead. + # + # @see http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html Ruby standard library `Set` + class Set < ::Set; + end + + elsif Concurrent.on_jruby? + require 'jruby/synchronized' + + # @!macro concurrent_Set + class Set < ::Set + include JRuby::Synchronized + end + + elsif Concurrent.on_rbx? || Concurrent.on_truffle? + require 'monitor' + require 'concurrent/thread_safe/util/array_hash_rbx' + + # @!macro concurrent_Set + class Set < ::Set + end + + ThreadSafe::Util.make_synchronized_on_rbx Set + end +end + diff --git a/spec/concurrent/array_spec.rb b/spec/concurrent/array_spec.rb index 6f20c9d8c..5d6c7f72a 100644 --- a/spec/concurrent/array_spec.rb +++ b/spec/concurrent/array_spec.rb @@ -13,6 +13,7 @@ module Concurrent end end end.map(&:join) + expect(ary).to be_empty end describe '#slice' do diff --git a/spec/concurrent/channel_spec.rb b/spec/concurrent/channel_spec.rb index b0bce1346..339f98588 100644 --- a/spec/concurrent/channel_spec.rb +++ b/spec/concurrent/channel_spec.rb @@ -611,7 +611,7 @@ module Concurrent latch.wait(10) expect(actual).to eq expected end - end + end context '.go_loop_via' do diff --git a/spec/concurrent/hash_spec.rb b/spec/concurrent/hash_spec.rb index 2e5e1061b..459e0a1ad 100644 --- a/spec/concurrent/hash_spec.rb +++ b/spec/concurrent/hash_spec.rb @@ -7,11 +7,12 @@ module Concurrent Thread.new do 1000.times do |j| hsh[i * 1000 + j] = i - hsh[i * 1000 + j] - hsh.delete(i * 1000 + j) + expect(hsh[i * 1000 + j]).to eq(i) + expect(hsh.delete(i * 1000 + j)).to eq(i) end end end.map(&:join) + expect(hsh).to be_empty end end end diff --git a/spec/concurrent/set_spec.rb b/spec/concurrent/set_spec.rb new file mode 100644 index 000000000..6fe8766b1 --- /dev/null +++ b/spec/concurrent/set_spec.rb @@ -0,0 +1,20 @@ +require 'set' +module Concurrent + RSpec.describe Set do + let!(:set) { described_class.new } + + it 'concurrency' do + (1..THREADS).map do |i| + Thread.new do + 1000.times do + v = i + set << v + expect(set).not_to be_empty + set.delete(v) + end + end + end.map(&:join) + expect(set).to be_empty + end + end +end