dispatch_queue_rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/LICENSE +22 -0
  4. data/README.md +98 -0
  5. data/dispatch_queue_rb.gemspec +43 -0
  6. data/lib/dispatch_queue_rb.rb +32 -0
  7. data/lib/dispatch_queue_rb/concurrent_queue.rb +116 -0
  8. data/lib/dispatch_queue_rb/dispatch.rb +66 -0
  9. data/lib/dispatch_queue_rb/dispatch_group.rb +72 -0
  10. data/lib/dispatch_queue_rb/internal/condition_variable_pool.rb +33 -0
  11. data/lib/dispatch_queue_rb/internal/continuation.rb +41 -0
  12. data/lib/dispatch_queue_rb/internal/heap.rb +88 -0
  13. data/lib/dispatch_queue_rb/internal/thread_pool_queue.rb +127 -0
  14. data/lib/dispatch_queue_rb/internal/thread_queue.rb +62 -0
  15. data/lib/dispatch_queue_rb/internal/timer_pool.rb +71 -0
  16. data/lib/dispatch_queue_rb/mixins/dispatch_after_impl.rb +18 -0
  17. data/lib/dispatch_queue_rb/mixins/dispatch_sync_impl.rb +42 -0
  18. data/lib/dispatch_queue_rb/serial_queue.rb +77 -0
  19. data/lib/dispatch_queue_rb/version.rb +12 -0
  20. data/rakefile.rb +110 -0
  21. data/test/_test_env.rb +52 -0
  22. data/test/test_concurrent_queue.rb +90 -0
  23. data/test/test_condition_variable_pool.rb +41 -0
  24. data/test/test_continuation.rb +23 -0
  25. data/test/test_dispatch.rb +91 -0
  26. data/test/test_dispatch_group.rb +59 -0
  27. data/test/test_group_concurrent_queue.rb +75 -0
  28. data/test/test_group_serial_queue.rb +33 -0
  29. data/test/test_group_thread_pool_queue.rb +34 -0
  30. data/test/test_heap.rb +58 -0
  31. data/test/test_serial_queue.rb +77 -0
  32. data/test/test_thread_pool_queue.rb +63 -0
  33. data/test/test_thread_queue.rb +77 -0
  34. data/test/test_timer_pool.rb +124 -0
  35. data/test/test_version.rb +155 -0
  36. metadata +181 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e177e6f49e44b8c2e8aa0e34ffdb8acf2dc2a555
4
+ data.tar.gz: ce6d7aed534ffe9ccb29f604ea28358b58c70cbb
5
+ SHA512:
6
+ metadata.gz: feb77bdb4577eb050c0c0e9544b3bcba7eeda6774638a4bfe1beb7e8dd04b4a30a778310b0bb2d7b085a178d4623d7e38948b88ffe156c157172155d69758cfc
7
+ data.tar.gz: f2687609268da36638c6aabbdea5dcbda827dc82559701774b550881ee6e3dec2f33f1bbb175e29641e7016ce0f42c52285d30c4b47db7904f667c24b78bd0aa
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : Gemfile
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+
12
+ source 'https://rubygems.org'
13
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # DispatchQueueRb
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/dispatch_queue_rb.svg)](https://badge.fury.io/rb/dispatch_queue_rb)
4
+ [![Build Status](https://travis-ci.org/marcus999/dispatch_queue_rb.svg?branch=master)](https://travis-ci.org/marcus999/dispatch_queue_rb)
5
+ [![Code Climate](https://codeclimate.com/github/marcus999/dispatch_queue_rb/badges/gpa.svg)](https://codeclimate.com/github/marcus999/dispatch_queue_rb)
6
+ [![Issue Count](https://codeclimate.com/github/marcus999/dispatch_queue_rb/badges/issue_count.svg)](https://codeclimate.com/github/marcus999/dispatch_queue_rb)
7
+
8
+
9
+
10
+ ## Overview
11
+
12
+ DispatchQueueRb is a pure ruby implementation of the Grand Central Dispatch
13
+ concurrency primitives, using Ruby threads and blocking synchronization primitives
14
+ like Mutex and ConditionVariable.
15
+
16
+ It implements serial and concurrent queues, with synchronous, asynchronous,
17
+ barrier and delayed dispatch methods. All queues dispatch methods support an
18
+ optional dispatch groups to synchronize on completion of a group of tasks.
19
+ It also provides a thread pool based concurrent queue, scaled to the number
20
+ of available cpu cores, and used by default to schedule the actual work.
21
+
22
+ Beside a highly optimized lock-free C implementation with libdispatch,
23
+ Grand Central Dispatch is a shift of paradigm that expresses concurrent
24
+ programming concepts in terms of tasks that must be serialized with respect to
25
+ each other and tasks that can be performed concurrently.
26
+
27
+ Using threads, concurrency is usually defining with a fixed set of threads that
28
+ perform specific tasks, and by passing data between threads using message queues
29
+ and producer / consumer patterns. The flow of data and the sequence of
30
+ operations is often very inflexible because what threads do and where they
31
+ write their result to is often frozen at design time.
32
+
33
+ With dispatch queues, concurrency is expressed by scheduling work items to
34
+ serial and concurrent queues. The work that needs to be performed in the
35
+ context of a queue is not frozen in a dedicated thread, but passed as a block
36
+ of code to the dispatch method call. That block of code captures the data it
37
+ needs to execute, knows how to access global immutable data, and defines what
38
+ to do with the result.
39
+
40
+
41
+ ### Implements:
42
+ - SerialQueue and ConcurrentQueue
43
+ - dispatch_async() and dispatch_sync()
44
+ - dispatch_barrier_async() and dispatch_barrier_sync()
45
+ - dispatch_after()
46
+ - Dispatch.main_queue is a global serial queue attached to a single thread
47
+ (but not the main thread).
48
+ - Dispatch.default_queue is a global concurrent queue that is implemented as
49
+ a thread pool, scaled to the number of available cpu cores. It is the
50
+ default parent_queue for all private queues.
51
+
52
+ ### Key differences and not supported features:
53
+ - Implemented with Ruby threading primitives (Mutex, ConditionVariable) instead
54
+ of high-performance lock-free algorithms
55
+ - Aimed at MRI ruby 2.x, where ruby code cannot execute concurrently across
56
+ multiple cpu cores. Mostly useful to manage parallel sub-processes execution.
57
+ - Dispatch.default_queue does not monitor thread activity and does not spawn
58
+ more threads to compensate for blocked threads.
59
+
60
+ ### Version 1.0 limitations:
61
+ - Does not implement an equivalent for dispatch_source primitives.
62
+ - Does not support suspend / resume operations
63
+ - Does not provide multiple priority levels for global queues
64
+
65
+
66
+ ## Installation
67
+
68
+ Add this line to your application's Gemfile:
69
+
70
+ ```ruby
71
+ gem 'dispatch_queue_rb'
72
+ ```
73
+
74
+ And then execute:
75
+
76
+ $ bundle
77
+
78
+ Or install it yourself as:
79
+
80
+ $ gem install dispatch_queue_rb
81
+
82
+ ## Usage
83
+
84
+ TODO: Write usage instructions here
85
+
86
+ ## Development
87
+
88
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
89
+
90
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( https://github.com/[my-github-username]/dispatch_queue_rb/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create a new Pull Request
@@ -0,0 +1,43 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : dispatch_queue_rb.gemspec
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+ require_relative 'lib/dispatch_queue_rb/version.rb'
12
+
13
+ Gem::Specification.new do |spec|
14
+ spec.name = 'dispatch_queue_rb'
15
+ spec.version = DispatchQueue::VERSION
16
+ spec.authors = ["Marc-Antoine Argenton"]
17
+ spec.email = ["maargenton.dev@gmail.com"]
18
+ spec.summary = "Pure ruby implementation of Grand Central Dispatch concurrency primitives."
19
+ spec.description = %q{
20
+ DispatchQueueRb is a pure ruby implementation of Grand Central Dispatch concurrency primitives.
21
+ It implements serial and concurrent queues, with synchronous, asynchronous,
22
+ barrier and delayed dispatch methods. All queues dispatch methods support an
23
+ optional dispatch groups to synchronize on completion of a group of tasks.
24
+ It also provides a thread pool based concurrent queue, scaled to the number
25
+ of available cpu cores, and used by default to schedule the actual work.
26
+ }.gsub( /\s+/, ' ').strip
27
+ spec.homepage = "https://github.com/marcus999/dispatch_queue_rb"
28
+
29
+ spec.files = Dir['[A-Z]*', 'rakefile.rb', '*.gemspec'].reject { |f| f =~ /.lock/ }
30
+ spec.files += Dir['bin/**', 'lib/**/*.rb', 'test/**/*.rb', 'spec/**/*.rb', 'features/**/*.rb']
31
+ spec.executables = spec.files.grep( %r{^bin/} ) { |f| File.basename(f) }
32
+ spec.test_files = spec.files.grep( %r{^(test|spec|features)/} )
33
+
34
+ # spec.add_runtime_dependency 'facets', '~> 3.0'
35
+ # spec.add_runtime_dependency 'mustache', '~> 1.0'
36
+
37
+ spec.add_development_dependency 'bundler', '~> 1.7'
38
+ spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'watch', '~> 0.1'
40
+ spec.add_development_dependency 'rr', '~> 1.1'
41
+ spec.add_development_dependency 'minitest', '~> 5.3'
42
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
43
+ end
@@ -0,0 +1,32 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb.rb
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ require 'set'
11
+
12
+ require_relative 'dispatch_queue_rb/version.rb'
13
+
14
+ require_relative 'dispatch_queue_rb/mixins/dispatch_sync_impl.rb'
15
+ require_relative 'dispatch_queue_rb/mixins/dispatch_after_impl.rb'
16
+
17
+ require_relative 'dispatch_queue_rb/internal/condition_variable_pool.rb'
18
+ require_relative 'dispatch_queue_rb/internal/heap.rb'
19
+ require_relative 'dispatch_queue_rb/internal/continuation.rb'
20
+ require_relative 'dispatch_queue_rb/internal/thread_pool_queue.rb'
21
+ require_relative 'dispatch_queue_rb/internal/timer_pool.rb'
22
+ require_relative 'dispatch_queue_rb/internal/thread_queue.rb'
23
+
24
+ require_relative 'dispatch_queue_rb/dispatch.rb'
25
+ require_relative 'dispatch_queue_rb/serial_queue.rb'
26
+ require_relative 'dispatch_queue_rb/concurrent_queue.rb'
27
+ require_relative 'dispatch_queue_rb/dispatch_group.rb'
28
+
29
+ Dispatch = DispatchQueue::Dispatch
30
+ SerialQueue = DispatchQueue::SerialQueue
31
+ ConcurrentQueue = DispatchQueue::ConcurrentQueue
32
+ DispatchGroup = DispatchQueue::DispatchGroup
@@ -0,0 +1,116 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/concurrent_queue.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class ConcurrentQueue
12
+
13
+ def initialize( parent_queue: nil )
14
+ @mutex = Mutex.new
15
+ @condition = ConditionVariable.new
16
+ @task_list = []
17
+ @parent_queue = parent_queue || Dispatch.default_queue
18
+ @scheduled_count = 0
19
+ @barrier_count = 0
20
+ end
21
+
22
+ def dispatch_async( group:nil, &task )
23
+ group.enter() if group
24
+ continuation = Continuation.new( target_queue:@parent_queue, group:group ) do
25
+ _run_task( task, false )
26
+ end
27
+
28
+ schedule_immediately = @mutex.synchronize do
29
+ if ( @barrier_count > 0)
30
+ @task_list << continuation
31
+ false
32
+ else
33
+ @scheduled_count += 1
34
+ true
35
+ end
36
+ end
37
+
38
+ continuation.run() if schedule_immediately
39
+ self
40
+ end
41
+
42
+ def dispatch_barrier_async( group:nil, &task )
43
+ group.enter() if group
44
+ continuation = Continuation.new( target_queue:@parent_queue, group:group, barrier:true ) do
45
+ _run_task( task, true )
46
+ end
47
+
48
+ barrier_task, tasks = @mutex.synchronize do
49
+ @barrier_count += 1
50
+ @task_list << continuation
51
+ resume_pending = ( @scheduled_count == 0 && @barrier_count == 1)
52
+ _sync_get_next_batch() if resume_pending
53
+ end
54
+
55
+ _schedule_next_batch( barrier_task, tasks )
56
+ end
57
+
58
+ include DispatchSyncImpl
59
+ include DispatchAfterImpl
60
+
61
+
62
+ # def _debug_trace_queue_state( prefix = "" )
63
+ # puts "%-35s | scheduled: %3d, barrier: %3d, queued: %3d, barrier_head: %-5s" % [
64
+ # prefix,
65
+ # @scheduled_count,
66
+ # @barrier_count,
67
+ # @task_list.count,
68
+ # !@task_list.empty? && @task_list.first[1],
69
+ # ]
70
+ # end
71
+
72
+ private
73
+ def _run_task( task, barrier )
74
+ previous_queue = Thread.current[:current_queue]
75
+ Thread.current[:current_queue] = self
76
+
77
+ begin
78
+ task.call()
79
+ ensure
80
+ Thread.current[:current_queue] = previous_queue
81
+ _task_comleted( barrier )
82
+ end
83
+ end
84
+
85
+ def _task_comleted( barrier = false )
86
+ barrier_task, tasks = @mutex.synchronize do
87
+ resume_pending = barrier || ((@scheduled_count -= 1) == 0)
88
+ @barrier_count -= 1 if barrier
89
+ _sync_get_next_batch() if resume_pending
90
+ end
91
+
92
+ _schedule_next_batch( barrier_task, tasks )
93
+ end
94
+
95
+ def _sync_get_next_batch()
96
+ return nil if @task_list.empty?
97
+ return @task_list.shift if @task_list.first.barrier
98
+
99
+ tasks = []
100
+ tasks << @task_list.shift while !@task_list.first.barrier
101
+ @scheduled_count += tasks.count
102
+ return [nil, tasks]
103
+ end
104
+
105
+ def _schedule_next_batch( barrier_task, tasks )
106
+ if barrier_task
107
+ barrier_task.run()
108
+ elsif tasks
109
+ tasks.each do |t|
110
+ t.run()
111
+ end
112
+ end
113
+ end
114
+
115
+ end # class ConcurrentQueue
116
+ end # module DispatchQueue
@@ -0,0 +1,66 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/dispatch.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ module Dispatch
12
+
13
+ Result = Struct.new( :value )
14
+
15
+ class << self
16
+ def ncpu()
17
+ @@ncpu ||= `sysctl -n hw.ncpu`.to_i rescue 1
18
+ end
19
+
20
+ def default_queue
21
+ @@default_queue
22
+ end
23
+
24
+ def main_queue
25
+ @@main_queue
26
+ end
27
+
28
+ def synchronize()
29
+ mutex, condition = ConditionVariablePool.acquire()
30
+ result = nil
31
+ result_handler = Proc.new { |r|
32
+ result = r;
33
+ mutex.synchronize { condition.signal() }
34
+ }
35
+ mutex.synchronize do
36
+ yield result_handler
37
+ condition.wait( mutex )
38
+ end
39
+ ConditionVariablePool.release( mutex, condition )
40
+ result
41
+ end
42
+
43
+ def concurrent_map( input_array, target_queue:nil, &task )
44
+ group = DispatchGroup.new
45
+ target_queue ||= default_queue
46
+
47
+ output_results = input_array.map do |e|
48
+ result = Result.new
49
+ target_queue.dispatch_async( group:group ) do
50
+ result.value = task.call( e )
51
+ end
52
+ result
53
+ end
54
+
55
+ group.wait()
56
+ output_results.map { |result| result.value }
57
+ end
58
+
59
+
60
+ private
61
+ @@default_queue = ThreadPoolQueue.new()
62
+ @@main_queue = ThreadQueue.new()
63
+
64
+ end # class << self
65
+ end # class Dispatch
66
+ end # module DispatchQueue
@@ -0,0 +1,72 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/dispatch_group.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class DispatchGroup
12
+ def initialize()
13
+ @mutex = Mutex.new
14
+ @condition = ConditionVariable.new
15
+ @count = 0
16
+ @notify_list = []
17
+ end
18
+
19
+ def enter()
20
+ @mutex.synchronize { @count += 1 }
21
+ self
22
+ end
23
+
24
+ def leave()
25
+ notify_list = @mutex.synchronize do
26
+ @count -= 1
27
+ raise "Unbalanced calls to DispatchGroup.enter() / .leave()" if @count < 0
28
+ if @count == 0
29
+ @condition.broadcast()
30
+ _sync_swap_notify_list()
31
+ end
32
+ end
33
+
34
+ _schedule_notify_list( notify_list ) if notify_list
35
+ self
36
+ end
37
+
38
+ def notify( target_queue:nil, barrier:false, group:nil, &task )
39
+ continuation = Continuation.new( target_queue:target_queue,
40
+ barrier:barrier, group:group, &task )
41
+ @mutex.synchronize do
42
+ if @count == 0
43
+ continuation.run( default_target_queue:Dispatch.default_queue )
44
+ else
45
+ @notify_list << continuation
46
+ end
47
+ end
48
+ self
49
+ end
50
+
51
+ def wait( timeout:nil )
52
+ @mutex.synchronize do
53
+ return true if @count == 0
54
+ @condition.wait( @mutex, timeout )
55
+ return @count == 0
56
+ end
57
+ end
58
+
59
+ private
60
+ def _sync_swap_notify_list()
61
+ return nil if @notify_list.empty?
62
+ notify_list = @notify_list
63
+ @notify_list = []
64
+ return notify_list
65
+ end
66
+
67
+ def _schedule_notify_list( notify_list )
68
+ notify_list.each { |continuation| continuation.run() }
69
+ end
70
+
71
+ end # class DispatchGroup
72
+ end # module DispatchQueue