Skip to content

Commit fb51736

Browse files
committed
Merge pull request #111 from ruby-concurrency/atomic-reference-with-actress
Atomic Reference with Actress
2 parents fb91666 + f73f9d8 commit fb51736

34 files changed

+1240
-222
lines changed

.gitignore

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Gemfile.lock
2-
tests.txt
32
*.gem
3+
lib/1.8
4+
lib/1.9
5+
lib/2.0
46
.rvmrc
57
.ruby-version
68
.ruby-gemset
@@ -21,9 +23,14 @@ coverage
2123
.DS_Store
2224
TAGS
2325
tmtags
24-
*.swo
25-
*.swp
26+
*.sw?
2627
.idea
2728
.rbx/*
28-
*.py
29-
*.pyc
29+
lib/*.bundle
30+
lib/*.so
31+
lib/*.jar
32+
ext/*.bundle
33+
ext/*.so
34+
ext/*.jar
35+
pkg
36+
*.gem

.travis.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
language: ruby
22
rvm:
3-
- 2.1.0
3+
- 2.1.2
44
- 2.0.0
55
- 1.9.3
66
- ruby-head
77
- jruby-19mode
88
- jruby-head
99
- rbx-2
10+
jdk:
11+
- oraclejdk8
1012
branches:
1113
only:
1214
- master
@@ -15,4 +17,4 @@ matrix:
1517
- rvm: ruby-head
1618
- rvm: jruby-head
1719
- rvm: 1.9.3
18-
script: "bundle exec rspec --color --backtrace --seed 1 --format documentation ./spec"
20+
script: "bundle exec rake compile && bundle exec rspec --color --backtrace --seed 1 --format documentation ./spec"

Gemfile

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ source 'https://rubygems.org'
22

33
gemspec
44

5-
6-
group :development do
7-
gem 'rake', '~> 10.2.2'
8-
gem 'countloc', '~> 0.4.0', platforms: :mri
9-
gem 'yard', '~> 0.8.7.4'
10-
gem 'inch', '~> 0.4.1', platforms: :mri
11-
gem 'redcarpet', platforms: :mri # understands github markdown
12-
end
5+
gem 'rake', '~> 10.3.2'
6+
gem 'rake-compiler', '~> 0.9.2'
137

148
group :testing do
159
gem 'rspec', '~> 2.14.1'
16-
gem 'simplecov', '~> 0.8.2'
17-
gem 'coveralls', '~> 0.7.0', require: false
10+
gem 'simplecov', '~> 0.8.2', :require => false
11+
gem 'coveralls', '~> 0.7.0', :require => false
1812
gem 'timecop', '~> 0.7.1'
1913
end
14+
15+
group :documentation do
16+
gem 'countloc', '~> 0.4.0', :platforms => :mri, :require => false
17+
gem 'yard', '~> 0.8.7.4', :require => false
18+
gem 'inch', '~> 0.4.1', :platforms => :mri, :require => false
19+
gem 'redcarpet', '~> 3.1.2', platforms: :mri # understands github markdown
20+
end

Rakefile

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,87 @@
1-
$:.push File.join(File.dirname(__FILE__), 'lib')
2-
$:.push File.join(File.dirname(__FILE__), 'tasks/support')
3-
4-
require 'rubygems'
51
require 'bundler/gem_tasks'
6-
require 'rspec'
7-
require 'rspec/core/rake_task'
2+
require 'rake/extensiontask'
3+
require 'rake/javaextensiontask'
84

9-
require 'concurrent'
5+
GEMSPEC = Gem::Specification.load('concurrent-ruby.gemspec')
6+
EXTENSION_NAME = 'concurrent_ruby_ext'
107

118
Bundler::GemHelper.install_tasks
129

13-
RSpec::Core::RakeTask.new(:spec)
14-
$:.unshift 'tasks'
15-
Dir.glob('tasks/**/*.rake').each do|rakefile|
16-
load rakefile
10+
$:.push File.join(File.dirname(__FILE__), 'lib')
11+
require 'extension_helper'
12+
13+
def safe_load(file)
14+
begin
15+
load file
16+
rescue LoadError => ex
17+
puts 'Error loading rake tasks, but will continue...'
18+
puts ex.message
19+
end
1720
end
1821

19-
RSpec::Core::RakeTask.new(:travis_spec) do |t|
20-
t.rspec_opts = '--tag ~@not_on_travis'
22+
Dir.glob('tasks/**/*.rake').each do |rakefile|
23+
safe_load rakefile
2124
end
2225

23-
task :default => [:travis_spec]
26+
desc 'Run benchmarks'
27+
task :bench do
28+
exec 'ruby -Ilib -Iext examples/bench_atomic.rb'
29+
end
30+
31+
if defined?(JRUBY_VERSION)
32+
33+
Rake::JavaExtensionTask.new(EXTENSION_NAME, GEMSPEC) do |ext|
34+
ext.ext_dir = 'ext'
35+
end
36+
37+
elsif Concurrent.use_c_extensions?
38+
39+
Rake::ExtensionTask.new(EXTENSION_NAME, GEMSPEC) do |ext|
40+
ext.ext_dir = "ext/#{EXTENSION_NAME}"
41+
ext.cross_compile = true
42+
ext.cross_platform = ['x86-mingw32', 'x64-mingw32']
43+
end
44+
45+
ENV['RUBY_CC_VERSION'].to_s.split(':').each do |ruby_version|
46+
platforms = {
47+
'x86-mingw32' => 'i686-w64-mingw32',
48+
'x64-mingw32' => 'x86_64-w64-mingw32'
49+
}
50+
platforms.each do |platform, prefix|
51+
task "copy:#{EXTENSION_NAME}:#{platform}:#{ruby_version}" do |t|
52+
%w[lib tmp/#{platform}/stage/lib].each do |dir|
53+
so_file = "#{dir}/#{ruby_version[/^\d+\.\d+/]}/#{EXTENSION_NAME}.so"
54+
if File.exists?(so_file)
55+
sh "#{prefix}-strip -S #{so_file}"
56+
end
57+
end
58+
end
59+
end
60+
end
61+
else
62+
task :clean
63+
task :compile
64+
task "compile:#{EXTENSION_NAME}"
65+
end
66+
67+
Rake::Task[:clean].enhance do
68+
rm_rf 'pkg/classes'
69+
rm_rf 'tmp'
70+
rm_rf 'lib/1.9'
71+
rm_rf 'lib/2.0'
72+
rm_f Dir.glob('./lib/*.jar')
73+
rm_f Dir.glob('./**/*.bundle')
74+
end
75+
76+
begin
77+
require 'rspec'
78+
require 'rspec/core/rake_task'
79+
80+
RSpec::Core::RakeTask.new(:spec) do |t|
81+
t.rspec_opts = '--color --backtrace --format documentation'
82+
end
83+
84+
task :default => [:clean, :compile, :spec]
85+
rescue LoadError
86+
puts 'Error loading Rspec rake tasks, probably building the gem...'
87+
end

concurrent-ruby.gemspec

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ Gem::Specification.new do |s|
1818
Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns.
1919
EOF
2020

21-
s.files = Dir['README*', 'LICENSE*', 'CHANGELOG*']
22-
s.files += Dir['{lib,md,spec}/**/*']
23-
s.test_files = Dir['{spec}/**/*']
24-
s.extra_rdoc_files = ['README.md']
21+
s.files = Dir['lib/**/*']
2522
s.extra_rdoc_files = Dir['README*', 'LICENSE*', 'CHANGELOG*']
2623
s.require_paths = ['lib']
2724

25+
if defined?(JRUBY_VERSION)
26+
s.files += Dir['lib/concurrent_ruby_ext.jar']
27+
s.platform = 'java'
28+
elsif ! ENV['BUILD_PURE_RUBY']
29+
s.extensions = 'ext/concurrent_ruby_ext/extconf.rb'
30+
s.files += Dir['ext/**/*.{h,c,cpp}']
31+
end
32+
2833
s.required_ruby_version = '>= 1.9.3'
2934
end

examples/atomic_example.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require 'concurrent'
2+
3+
my_atomic = Concurrent::Atomic.new(0)
4+
my_atomic.update {|v| v + 1}
5+
puts "new value: #{my_atomic.value}"
6+
7+
begin
8+
my_atomic.try_update {|v| v + 1}
9+
rescue Concurrent::Atomic::ConcurrentUpdateError => cue
10+
# deal with it (retry, propagate, etc)
11+
end
12+
puts "new value: #{my_atomic.value}"

examples/bench_atomic.rb

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
require 'benchmark'
2+
require 'rbconfig'
3+
require 'thread'
4+
require 'concurrent'
5+
Thread.abort_on_exception = true
6+
7+
$go = false # for synchronizing parallel threads
8+
9+
# number of updates on the value
10+
N = ARGV[1] ? ARGV[1].to_i : 100_000
11+
12+
# number of threads for parallel test
13+
M = ARGV[0] ? ARGV[0].to_i : 10
14+
15+
# list of platform-specific implementations
16+
ATOMICS = [
17+
'MutexAtomic',
18+
'CAtomic',
19+
'JavaAtomic',
20+
'RbxAtomic',
21+
]
22+
23+
puts "Testing with #{RbConfig::CONFIG['ruby_install_name']} #{RUBY_VERSION}"
24+
25+
puts
26+
puts '*** Sequential updates ***'
27+
Benchmark.bm(10) do |x|
28+
value = 0
29+
x.report 'no lock' do
30+
N.times do
31+
value += 1
32+
end
33+
end
34+
35+
@lock = Mutex.new
36+
x.report 'mutex' do
37+
value = 0
38+
N.times do
39+
@lock.synchronize do
40+
value += 1
41+
end
42+
end
43+
end
44+
45+
ATOMICS.each do |clazz|
46+
if Concurrent.const_defined? clazz
47+
@atom = Concurrent.const_get(clazz).new(0)
48+
x.report clazz do
49+
N.times do
50+
@atom.update{|x| x += 1}
51+
end
52+
end
53+
end
54+
end
55+
end
56+
57+
def para_setup(num_threads, count, &block)
58+
if num_threads % 2 > 0
59+
raise ArgumentError, 'num_threads must be a multiple of two'
60+
end
61+
raise ArgumentError, 'need block' unless block_given?
62+
63+
# Keep those threads together
64+
tg = ThreadGroup.new
65+
66+
num_threads.times do |i|
67+
diff = (i % 2 == 0) ? 1 : -1
68+
69+
t = Thread.new do
70+
nil until $go
71+
count.times do
72+
yield diff
73+
end
74+
end
75+
76+
tg.add(t)
77+
end
78+
79+
# Make sure all threads are started
80+
while tg.list.find{|t| t.status != 'run'}
81+
Thread.pass
82+
end
83+
84+
# For good measure
85+
GC.start
86+
87+
tg
88+
end
89+
90+
def para_run(tg)
91+
$go = true
92+
tg.list.each{|t| t.join}
93+
$go = false
94+
end
95+
96+
puts
97+
puts '*** Parallel updates ***'
98+
Benchmark.bm(10) do |bm|
99+
# This is not secure
100+
value = 0
101+
tg = para_setup(M, N/M) do |diff|
102+
value += diff
103+
end
104+
bm.report('no lock'){ para_run(tg) }
105+
106+
value = 0
107+
@lock = Mutex.new
108+
tg = para_setup(M, N/M) do |diff|
109+
@lock.synchronize do
110+
value += diff
111+
end
112+
end
113+
bm.report('mutex'){ para_run(tg) }
114+
raise unless value == 0
115+
116+
ATOMICS.each do |clazz|
117+
if Concurrent.const_defined? clazz
118+
@atom = Concurrent.const_get(clazz).new(0)
119+
tg = para_setup(M, N/M) do |diff|
120+
@atom.update{|x| x + diff}
121+
end
122+
bm.report(clazz){ para_run(tg) }
123+
raise unless @atom.value == 0
124+
end
125+
end
126+
end

0 commit comments

Comments
 (0)