multiprocessing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in multiprocessing.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 clicube
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ MultiProcessing
2
+ ====================
3
+
4
+ MultiProcessingはRubyにおいてプロセス間同期とプロセス間通信の機能を提供します(することを目指しています).
5
+ 各クラスはRubyの標準添付ライブラリthreadで提供されているクラスのような動作をすることを目指しています.
6
+
7
+ MultiProcessing includes classes for inter-process synchronization and communication.
8
+ The classes can be used like ones in ruby standard library for thread.
9
+
10
+ 現状で使用できるクラスは以下です.
11
+ 各クラスはMultiProcessingモジュールの下に作成されています.
12
+
13
+ * Mutex
14
+ * ConditionVariable
15
+ * Semaphore
16
+ * Queue
17
+ * Process
18
+ * ExternalObject
19
+
20
+ また,IO.named\_pipe が追加されます
21
+
22
+ いずれのクラスもプロセス間通信にパイプ(IO.pipe)を使用しています.
23
+ また,複数のプロセスで1つの同期オブジェクトを共有するためにforkを使用する事を想定しているため,
24
+ Unix系OSのみでのみ使用する事ができます.
25
+
26
+ (Windowsするためには,名前付き共有セマフォなどの同期機能を使用する必要がありそうですね・・・)
27
+
28
+ Mutex
29
+ ----------------
30
+
31
+ 標準添付ライブラリthreadのMutexと同様の使い方をします.
32
+
33
+ m = MultiProcessing::Mutex.new
34
+ fork do
35
+ m.synchronize do
36
+ # some critical work
37
+ end
38
+ end
39
+ m.synchronize do
40
+ # come critical work
41
+ end
42
+ Process.waitall
43
+
44
+ lockした後unlockする前にforkすると(synchronize中でforkした場合も)そのクリティカルセクションは並列に動作してしまうので注意してください.
45
+ (forkしたときに子プロセスでは子スレッドが全て殺されるため,別のスレッドでforkすれば問題ないと思います.)
46
+
47
+ ConditionVariable
48
+ ----------------
49
+
50
+ 標準添付ライブラリthreadのConditionVariableと同じ使い方をします.
51
+
52
+ m = MultiProcessing::Mutex.new
53
+ cond = MultiProcessing::ConditionVariable.new
54
+ fork do
55
+ m.synchronize do
56
+ cond.wait(m)
57
+ # something to do
58
+ end
59
+ end
60
+ sleep 1
61
+ # on condition changed
62
+ cond.signal
63
+ Process.waitall
64
+
65
+ Semaphore
66
+ ----------------
67
+
68
+ Semaphoreは標準添付ライブラリに含まれていませんが作りました.
69
+
70
+ s = MultiProcessing::Semaphore.new 2
71
+
72
+ コンストラクタにリソース量の初期値を与えてください.
73
+ Pでリソースのロック,Vで解放をします.
74
+
75
+ Pはlock,Vはunlockという名前のエイリアスが用意してあります.
76
+
77
+ Queue
78
+ ----------------
79
+
80
+ Queueも標準threadライブラリのQueueと同様の使い方をします.
81
+
82
+ q = MultiProcessing::Quque.new
83
+ fork do
84
+ q.push :nyan
85
+ q.close.join_thread
86
+ end
87
+ p q.pop
88
+
89
+ プロセス間の通信にはパイプを使っています.Queue#pushをすると,パイプにデータを書き込むバックグラウンドスレッドが起動します(pythonのmultiprocessingを参考にしました).
90
+ プロセスが終了する際,バックグラウンドスレッドがパイプにデータを書き込み終わるまで待たないと,Queueが書き込みMutexをロックしたままになる場合があります.
91
+ 終了時は
92
+
93
+ q.close.join_thread
94
+
95
+ として,書き込みスレッドの終了を待ってください.close後にキューにデータをpushすると例外が発生します.
96
+
97
+ オブジェクトのシリアライズにMarshal.dumpを使用しているので,Marshal.dumpできないオブジェクトをpushできません.
98
+
99
+ Process
100
+ ----------------
101
+
102
+ 標準のProcessモジュールは
103
+
104
+ >Process がプロセスを表現するクラスではなく、プロセスに対する操作 をまとめたモジュールであることに注意してください。
105
+
106
+ とのことなので,プロセスを表現するっぽいクラスを作ってみました.
107
+
108
+ p = MultiProcessing::Process.new { # something to do }
109
+ p.join
110
+
111
+ とかできます.
112
+
113
+ その他
114
+ ----------------
115
+
116
+ ### 関係ありそうなライブラリ
117
+
118
+ * https://github.com/pmahoney/process_shared
119
+
120
+ ### TODO
121
+
122
+ * ConditionVariable, ExternalObject, IO.named\_pipe のSpecを書く
123
+
124
+ ライセンス
125
+ ----------------
126
+
127
+ Author: clicube
128
+
129
+ MIT License
130
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/mutex')
2
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/conditionvariable')
3
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/semaphore')
4
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/queue')
5
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/process')
6
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/namedpipe')
7
+ require File.expand_path(File.dirname(__FILE__) + '/multiprocessing/externalobject')
8
+
@@ -0,0 +1,84 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/processerror')
2
+ require File.expand_path(File.dirname(__FILE__) + '/mutex')
3
+
4
+ module MultiProcessing
5
+ class ConditionVariable
6
+
7
+ def initialize
8
+ @waiting_pout,@waiting_pin = IO.pipe
9
+ @signal_pout,@signal_pin = IO.pipe
10
+ end
11
+
12
+ def broadcast
13
+ while(signal); end
14
+ end
15
+
16
+ def signal
17
+ begin
18
+ @waiting_pout.read_nonblock 1
19
+ @signal_pin.syswrite 1
20
+ #@signal_pin.write 1
21
+ #@signal_pin.flush
22
+ return true
23
+ rescue Errno::EAGAIN
24
+ return nil
25
+ end
26
+ end
27
+
28
+ def wait(mutex)
29
+ raise MultiProcessing::ProcessError.new("mutex must be instance of MultiProcessing::Mutex") if mutex.class != MultiProcessing::Mutex
30
+ @waiting_pin.syswrite 1
31
+ #@waiting_pin.write 1
32
+ #@waiting_pin.flush
33
+ mutex.unlock
34
+ @signal_pout.readpartial 1
35
+ mutex.lock
36
+ self
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ if __FILE__ == $0
43
+
44
+ m = MultiProcessing::Mutex.new
45
+ cond = MultiProcessing::ConditionVariable.new
46
+ fork do
47
+ m.synchronize do
48
+ sleep 1
49
+ puts "waiting p1"
50
+ cond.wait(m)
51
+ puts "restarted p1"
52
+ sleep 1
53
+ puts "end p1"
54
+ end
55
+ end
56
+ fork do
57
+ m.synchronize do
58
+ sleep 1
59
+ puts "waiting p2"
60
+ cond.wait(m)
61
+ puts "restarted p2"
62
+ sleep 1
63
+ puts "end p2"
64
+ end
65
+ end
66
+ fork do
67
+ m.synchronize do
68
+ sleep 1
69
+ puts "waiting p3"
70
+ cond.wait(m)
71
+ puts "restarted p3"
72
+ sleep 1
73
+ puts "end p3"
74
+ end
75
+ end
76
+ sleep 5
77
+ puts "cond signaling"
78
+ cond.signal
79
+ sleep 3
80
+ puts "cond broadcasting"
81
+ cond.broadcast
82
+ Process.waitall
83
+ end
84
+
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/mutex')
2
+ require File.expand_path(File.dirname(__FILE__) + '/queue')
3
+ require File.expand_path(File.dirname(__FILE__) + '/process')
4
+
5
+ module MultiProcessing
6
+ class ExternalObject < BasicObject
7
+
8
+ def initialize obj
9
+ @call_queue = Queue.new
10
+ @result_queue = Queue.new
11
+ @mutex = Mutex.new
12
+ @closed = false
13
+ @process = Process.new(obj){|obj| process_loop obj }
14
+ end
15
+
16
+ def process_loop obj
17
+ while true
18
+ *args = @call_queue.deq
19
+ result = nil
20
+ begin
21
+ result = obj.__send__ *args
22
+ rescue => e
23
+ result = e
24
+ end
25
+ @result_queue.enq result
26
+ end
27
+ end
28
+ private :process_loop
29
+
30
+ def send name, *args
31
+ @mutex.synchronize do
32
+ raise ProcessError.new("already closed") if @closed
33
+ @call_queue.enq [name, *args]
34
+ return @result_queue.deq
35
+ end
36
+ end
37
+
38
+ def close
39
+ @mutex.synchronize do
40
+ @closed = true
41
+ @process.kill :TERM
42
+ end
43
+ end
44
+
45
+ def method_missing *args
46
+ self.send *args
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+
53
+
54
+ if $0 == __FILE__
55
+ obj = MultiProcessing::ExternalObject.new({})
56
+ obj[:cat] = :nyan
57
+ obj[:dog] = :wan
58
+ p obj
59
+
60
+ obj_proxy = MultiProcessing::ExternalObject.new(obj)
61
+ obj_proxy[:human] = :nyan
62
+ p obj_proxy
63
+
64
+ obj_proxy.close
65
+ obj.close
66
+ end
67
+
@@ -0,0 +1,138 @@
1
+ require 'thread'
2
+ require File.expand_path(File.dirname(__FILE__) + '/processerror')
3
+
4
+ module MultiProcessing
5
+
6
+ class Mutex
7
+
8
+ def initialize
9
+ @pout,@pin = IO.pipe
10
+ @pin.syswrite 1
11
+ #@pin.write 1
12
+ #@pin.flush
13
+ end
14
+
15
+ def lock
16
+ unless @locking_pid == ::Process.pid && @locking_thread == Thread.current
17
+ @pout.readpartial 1
18
+ @locking_pid = ::Process.pid
19
+ @locking_thread = Thread.current
20
+ else
21
+ raise ProcessError.new "mutex was tried locking twice"
22
+ end
23
+ self
24
+ end
25
+
26
+ def locked?
27
+ begin
28
+ @pout.read_nonblock 1
29
+ @pin.syswrite 1
30
+ #@pin.write 1
31
+ #@pin.flush
32
+ return false
33
+ rescue Errno::EAGAIN => e
34
+ return true
35
+ end
36
+ end
37
+
38
+ def try_lock
39
+ begin
40
+ @pout.read_nonblock 1
41
+ @locking_thread = Thread.current
42
+ @locking_pid = ::Process.pid
43
+ return true
44
+ rescue Errno::EAGAIN
45
+ return false
46
+ end
47
+ end
48
+
49
+ def unlock
50
+ raise ProcessError.new("Attempt to unlock a mutex which is not locked") if !locked?
51
+ if @locking_pid == ::Process.pid && @locking_thread == Thread.current
52
+ @pin.syswrite 1
53
+ #@pin.write 1
54
+ #@pin.flush
55
+ @locking_pid = nil
56
+ @locking_thread = nil
57
+ return self
58
+ else
59
+ #return nil
60
+ raise ProcessError.new("mutex was tried unlocking in process/thread which didn't lock this mutex #{@locking_pid} #{::Process.pid}")
61
+ end
62
+ end
63
+
64
+ def synchronize
65
+ lock
66
+ begin
67
+ ret = yield
68
+ ensure
69
+ unlock if locked?
70
+ end
71
+ return ret
72
+ end
73
+
74
+ def sleep timeout=nil
75
+ unlock
76
+ timeout ? Kernel.sleep(timeout) : Kernel.sleep
77
+ lock
78
+ end
79
+ end
80
+ end
81
+
82
+ if __FILE__ == $0
83
+
84
+ puts "use lock and unlock"
85
+ m = MultiProcessing::Mutex.new
86
+ puts "locking mutex in main process(pid:#{Process.pid})"
87
+ m.lock
88
+ puts "locked mutex in main process(pid:#{Process.pid})"
89
+ pid1 = fork do
90
+ puts "locking mutex in child process(pid:#{Process.pid})"
91
+ m.lock
92
+ puts "locked mutex in child process(pid:#{Process.pid})"
93
+ sleep 1
94
+ puts "unlocking mutex in child process(pid:#{Process.pid})"
95
+ m.unlock
96
+ puts "unlocked mutex in child process(pid:#{Process.pid})"
97
+ exit
98
+ end
99
+ pid2 = fork do
100
+ puts "locking mutex in child process(pid:#{Process.pid})"
101
+ m.lock
102
+ puts "locked mutex in child process(pid:#{Process.pid})"
103
+ sleep 1
104
+ puts "unlocking mutex in child process(pid:#{Process.pid})"
105
+ m.unlock
106
+ puts "unlocked mutex in child process(pid:#{Process.pid})"
107
+ exit
108
+ end
109
+
110
+ sleep 1
111
+ puts "unlocking mutex in main process(pid:#{Process.pid})"
112
+ m.unlock
113
+ puts "unlocked mutex in main process(pid:#{Process.pid})"
114
+ Process.waitall
115
+
116
+
117
+ puts ""
118
+ puts "use synchrnize"
119
+ m = MultiProcessing::Mutex.new
120
+ if pid = fork
121
+ puts "synchronizing in main process(pid:#{Process.pid})"
122
+ m.synchronize do
123
+ puts "something to do in main process(pid:#{Process.pid})"
124
+ sleep 2
125
+ puts "end something in main process(pid:#{Process.pid})"
126
+ end
127
+ Process.waitpid pid
128
+ else
129
+ sleep 1
130
+ puts "synchronizing in child process(pid:#{Process.pid})"
131
+ m.synchronize do
132
+ puts "something to do in child process(pid:#{Process.pid})"
133
+ sleep 1
134
+ puts "end something in child process(pid:#{Process.pid})"
135
+ end
136
+ end
137
+ end
138
+
@@ -0,0 +1,25 @@
1
+ class IO
2
+
3
+ def self.named_pipe(path, create=true)
4
+ if !File.exist?(path)&& create
5
+ system "mkfifo #{path}"
6
+ end
7
+ raise IOError.new("it is not named pipe") if File.ftype(path) != "fifo"
8
+ pout = File.open(path, "r+")
9
+ pin = File.open(path, "w+")
10
+ return pout, pin
11
+ end
12
+
13
+ end
14
+
15
+ if __FILE__ == $0
16
+ pout, pin = IO.named_pipe("nyan")
17
+ 3.times do
18
+ pin.puts :nyan
19
+ end
20
+ pin.flush
21
+ 3.times do
22
+ puts pout.gets
23
+ end
24
+ File.delete "nyan"
25
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/processerror')
2
+
3
+ module MultiProcessing
4
+ class Process
5
+
6
+ attr_reader :pid
7
+
8
+ def initialize *args
9
+ raise ProcessError.new "mulst be called with a block" unless block_given?
10
+ @pid = fork do
11
+ yield *args
12
+ exit
13
+ end
14
+ end
15
+
16
+ def value
17
+ th = ::Process.detach(@pid)
18
+ th.value
19
+ end
20
+
21
+ def join timeout=nil
22
+ th = ::Process.detach(@pid)
23
+ th.join timeout
24
+ end
25
+
26
+ def kill signal
27
+ ::Process.kill signal, @pid
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ if $0 == __FILE__
34
+ include MultiProcessing
35
+
36
+ p = MultiProcessing::Process.new("nyan") do |str|
37
+ puts str
38
+ sleep 1
39
+ end
40
+ p p
41
+ p.join
42
+ end
43
+
@@ -0,0 +1,4 @@
1
+ module MultiProcessing
2
+ class ProcessError < StandardError; end
3
+ end
4
+
@@ -0,0 +1,134 @@
1
+ require 'thread'
2
+ require File.expand_path(File.dirname(__FILE__) + '/mutex')
3
+ require File.expand_path(File.dirname(__FILE__) + '/semaphore')
4
+
5
+ module MultiProcessing
6
+
7
+ class QueueError < StandardError; end
8
+
9
+ class Queue
10
+
11
+ def initialize
12
+ @count = Semaphore.new 0
13
+ @write_mutex = Mutex.new
14
+ @read_mutex = Mutex.new
15
+ @len_pout, @len_pin = IO.pipe
16
+ @data_pout, @data_pin = IO.pipe
17
+ @enq_queue = ::Queue.new
18
+ @queue_zero_cond = ::ConditionVariable.new
19
+ @closed = false
20
+ end
21
+
22
+ def clear
23
+ begin
24
+ loop do
25
+ @read_mutex.synchronize do
26
+ self.deq(true)
27
+ end
28
+ end
29
+ rescue Errno::EAGAIN
30
+ end
31
+ end
32
+
33
+ def empty?
34
+ length == 0
35
+ end
36
+
37
+ def length
38
+ return @count.value
39
+ end
40
+ alias :size :length
41
+ alias :count :length
42
+
43
+ def deq non_block=false
44
+ data = ""
45
+ @read_mutex.synchronize do
46
+ unless non_block
47
+ @count.wait
48
+ else
49
+ @count.trywait
50
+ end
51
+
52
+ buf = ""
53
+ len = nil
54
+ begin
55
+ c = @len_pout.readpartial 1
56
+ if c == "\n"
57
+ len = buf.to_i
58
+ else
59
+ buf << c
60
+ end
61
+ end while !len
62
+
63
+ begin
64
+ buf = @data_pout.readpartial len
65
+ len -= buf.bytesize
66
+ data << buf
67
+ end while len > 0
68
+ end
69
+ return Marshal.load(data)
70
+
71
+ end
72
+ alias :pop :deq
73
+ alias :shift :deq
74
+
75
+ def enq obj
76
+ raise QueueError.new("already closed") if @closed
77
+ unless(@enq_thread && @enq_thread.alive?)
78
+ @enq_queue.clear
79
+ @enq_thread = Thread.new &method(:enq_loop)
80
+ end
81
+ @enq_queue.enq(Marshal.dump(obj))
82
+ Thread.pass
83
+ end
84
+ alias :push :enq
85
+ alias :unshift :enq
86
+
87
+ def enq_loop
88
+ loop do
89
+ data = @enq_queue.deq
90
+ @write_mutex.synchronize do
91
+ @count.post
92
+ @len_pin.write data.length.to_s + "\n"
93
+ @len_pin.flush
94
+ @data_pin.write data
95
+ @data_pin.flush
96
+ end
97
+ Thread.exit if @closed && @enq_queue.length == 0
98
+ end
99
+ end
100
+ private :enq_loop
101
+
102
+ def close
103
+ @closed = true
104
+ self
105
+ end
106
+
107
+ def join_thread
108
+ raise QueueError.new("must be closed before join_thread") unless @closed
109
+ if @enq_thread && @enq_thread.alive?
110
+ @enq_thread.join
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ if __FILE__ == $0
118
+
119
+ q = MultiProcessing::Queue.new
120
+
121
+ q.push(0)
122
+ sleep 1
123
+ pid = fork
124
+ if !pid
125
+ q.push("111")
126
+ q.push({:a=>"a",:b=>123})
127
+ p q.pop
128
+ exit(0)
129
+ end
130
+ p q.pop
131
+ p q.pop
132
+ Process.waitall
133
+
134
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/mutex')
2
+ require File.expand_path(File.dirname(__FILE__) + '/conditionvariable')
3
+
4
+ module MultiProcessing
5
+ class Semaphore
6
+
7
+ def initialize count
8
+ @count_pout, @count_pin = IO.pipe
9
+ @count_pin.syswrite "1"*count
10
+ #@count_pin.write "1"*count
11
+ #@count_pin.flush
12
+ @mutex = Mutex.new
13
+ @cond = ConditionVariable.new
14
+ end
15
+
16
+ def count_nonsynchronize
17
+ n = 0
18
+ begin
19
+ loop do
20
+ @count_pout.read_nonblock 1
21
+ n += 1
22
+ end
23
+ rescue Errno::EAGAIN
24
+ @count_pin.syswrite "1"*n
25
+ #@count_pin.write "1"*n
26
+ #@count_pin.flush
27
+ end
28
+ return n
29
+ end
30
+ private :count_nonsynchronize
31
+
32
+ def count
33
+ @mutex.synchronize do
34
+ count_nonsynchronize
35
+ end
36
+ end
37
+ alias :value :count
38
+
39
+ def P
40
+ @mutex.synchronize do
41
+ while count_nonsynchronize == 0
42
+ @cond.wait(@mutex)
43
+ end
44
+ @count_pout.readpartial 1
45
+ end
46
+ return self
47
+ end
48
+ alias :lock :P
49
+ alias :wait :P
50
+
51
+ def try_P
52
+ begin
53
+ @mutex.synchronize do
54
+ @count_pout.read_nonblock 1
55
+ end
56
+ return true
57
+ rescue Errno::EAGAIN
58
+ return false
59
+ end
60
+ end
61
+ alias :try_lock :try_P
62
+ alias :try_wait :try_P
63
+
64
+ def V
65
+ @mutex.synchronize do
66
+ @count_pin.syswrite 1
67
+ #@count_pin.write 1
68
+ #@count_pin.flush
69
+ @cond.signal
70
+ end
71
+ return self
72
+ end
73
+ alias :signal :V
74
+ alias :unlock :V
75
+ alias :post :V
76
+
77
+ def synchronize
78
+ self.P
79
+ begin
80
+ yield
81
+ ensure
82
+ self.V
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ if $0 == __FILE__
90
+ s = MultiProcessing::Semaphore.new 1
91
+ fork
92
+ s.wait
93
+ puts Process.pid
94
+ sleep 1
95
+ s.post
96
+ end
@@ -0,0 +1,3 @@
1
+ module MultiProcessing
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'multiprocessing/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "multiprocessing"
8
+ gem.version = MultiProcessing::VERSION
9
+ gem.authors = ["clicube"]
10
+ gem.email = ["clicube@gmail.com"]
11
+ gem.description = %q{Classes for inter-process synchronization/communication like thread library in ruby standard library}
12
+ gem.summary = %q{Inter-process synchronization/communication}
13
+ gem.homepage = "https://github.com/clicube/multiprocessing"
14
+
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,109 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'timeout'
3
+
4
+ describe MultiProcessing::Mutex do
5
+
6
+ before do
7
+ @mutex = MultiProcessing::Mutex.new
8
+ end
9
+
10
+ it "#lock returns itself" do
11
+ @mutex.lock.should === @mutex
12
+ end
13
+
14
+ it "#unlock returns itself if locked" do
15
+ @mutex.lock
16
+ @mutex.unlock.should === @mutex
17
+ end
18
+
19
+ it "#unlock raises ProcessError if not locked" do
20
+ proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
21
+ end
22
+
23
+ it "#locked? returns false if not locked" do
24
+ @mutex.locked?.should be_false
25
+ end
26
+
27
+ it "#locked? returns true if locked" do
28
+ @mutex.lock
29
+ @mutex.locked?.should be_true
30
+ end
31
+
32
+ it "#lock, #unlock can locks/unlocks other process" do
33
+ process = MultiProcessing::Process.new do
34
+ sleep 0.1
35
+ @mutex.lock
36
+ @mutex.unlock
37
+ end
38
+ @mutex.lock
39
+ sleep 0.2
40
+ process.join(0).should be_nil
41
+ @mutex.unlock
42
+ proc{ timeout(1){ process.join } }.should_not raise_error Timeout::Error
43
+ end
44
+
45
+ it "#synchronize can synchronize with other process" do
46
+ process = MultiProcessing::Process.new do
47
+ sleep 0.1
48
+ @mutex.synchronize do
49
+ :nop
50
+ end
51
+ end
52
+ @mutex.synchronize do
53
+ sleep 0.2
54
+ process.join(0).should be_nil
55
+ end
56
+ proc{ timeout(1){ process.join } }.should_not raise_error Timeout::Error
57
+ end
58
+
59
+ it "#lock cannot lock twice" do
60
+ @mutex.lock
61
+ proc{
62
+ @mutex.lock
63
+ }.should raise_error MultiProcessing::ProcessError
64
+ end
65
+
66
+ it "#unlock raises ProcessError and does not unlock when unlocking by other processes" do
67
+ process = MultiProcessing::Process.new do
68
+ @mutex.lock
69
+ sleep 0.2
70
+ @mutex.unlock
71
+ end
72
+ sleep 0.1
73
+ proc{ @mutex.unlock }.should raise_error MultiProcessing::ProcessError
74
+ process.value.success?.should be_true
75
+ end
76
+
77
+ it "#try_lock returns true if succeed, and lock correctly" do
78
+ @mutex.try_lock.should be_true
79
+ @mutex.locked?.should be_true
80
+ @mutex.unlock.should === @mutex
81
+ end
82
+
83
+ it "#try_lock returns false if failed" do
84
+ process = MultiProcessing::Process.new do
85
+ @mutex.lock
86
+ sleep 0.2
87
+ end
88
+ sleep 0.1
89
+ @mutex.try_lock.should be_false
90
+ end
91
+
92
+ it "s sleep method unlocks and sleeps and re-locks itself" do
93
+ process = MultiProcessing::Process.new do
94
+ @mutex.lock
95
+ @mutex.sleep(0.2)
96
+ sleep 0.2
97
+ @mutex.unlock
98
+ end
99
+ sleep 0.1
100
+ @mutex.try_lock.should be_true
101
+ @mutex.unlock
102
+ sleep 0.2
103
+ @mutex.try_lock.should be_false
104
+ timeout(1){ process.value }.success?.should be_true
105
+ end
106
+
107
+
108
+ end
109
+
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MultiProcessing::Process do
4
+
5
+ it "can be created with block" do
6
+ MultiProcessing::Process.new{:nop}.should be_instance_of MultiProcessing::Process
7
+ end
8
+
9
+ it "cannot be created without block" do
10
+ proc{ MultiProcessing::Process.new }.should raise_error MultiProcessing::ProcessError
11
+ end
12
+
13
+ it "returns pid" do
14
+ MultiProcessing::Process.new{ :nop }.pid.should be_instance_of Fixnum
15
+ end
16
+
17
+ it "joins process" do
18
+ p = MultiProcessing::Process.new { :nop }
19
+ p.join.should_not be_nil
20
+ end
21
+
22
+ it "joins process with timeout" do
23
+ p = MultiProcessing::Process.new { :nop }
24
+ p.join(1).should_not be_nil
25
+ p = MultiProcessing::Process.new { sleep 1 }
26
+ p.join(0).should be_nil
27
+ end
28
+
29
+ it "returns value" do
30
+ p = MultiProcessing::Process.new { :nop }
31
+ p.value.should be_instance_of ::Process::Status
32
+ p = MultiProcessing::Process.new { :nop }
33
+ p.value.success?.should be_true
34
+ p = MultiProcessing::Process.new { exit 1 }
35
+ p.value.success?.should be_false
36
+ end
37
+
38
+ it "returns nil when process does not exist" do
39
+ p = MultiProcessing::Process.new { :nop }
40
+ p.join
41
+ p.value.should be_nil
42
+ end
43
+
44
+ it "can be kill" do
45
+ p = MultiProcessing::Process.new { sleep 1 }
46
+ p.kill :TERM
47
+ p.join(0).should be_nil
48
+ end
49
+
50
+ end
51
+
@@ -0,0 +1,140 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'timeout'
3
+
4
+ describe MultiProcessing::Queue do
5
+
6
+ it "can be created" do
7
+ MultiProcessing::Queue.new.should be_instance_of MultiProcessing::Queue
8
+ end
9
+
10
+ it "#length returns its length" do
11
+ queue = MultiProcessing::Queue.new
12
+ queue.length.should == 0
13
+ queue.push :a
14
+ sleep 0.1 # wait for enqueue
15
+ queue.length.should == 1
16
+ queue.push :b
17
+ sleep 0.1 # wait for enqueue
18
+ queue.length.should == 2
19
+ queue.pop
20
+ queue.length.should == 1
21
+ queue.pop
22
+ queue.length.should == 0
23
+ end
24
+
25
+ it "can pass object across processes" do
26
+ queue = MultiProcessing::Queue.new
27
+ data_list = [:nyan, [:cat, :dog]]
28
+ fork do
29
+ data_list.each do |data|
30
+ queue.push data
31
+ end
32
+ sleep 0.1
33
+ end
34
+ result = []
35
+ timeout(1) do
36
+ data_list.length.times do
37
+ result << queue.pop
38
+ end
39
+ end
40
+ result.should == data_list
41
+ end
42
+
43
+ it "can pass large objects across processes" do
44
+ queue1 = MultiProcessing::Queue.new
45
+ queue2 = MultiProcessing::Queue.new
46
+ data = "a" * 1024 * 1024 * 16 # 16MB
47
+ timeout_sec = 10
48
+ # process to echo queue1 -> queue 2
49
+ process = MultiProcessing::Process.new do
50
+ echo_queue = ::Queue.new
51
+ Thread.new do
52
+ loop do
53
+ echo_queue.push queue1.pop
54
+ end
55
+ end
56
+ Thread.new do
57
+ loop do
58
+ queue2.push echo_queue.pop
59
+ end
60
+ end
61
+ sleep timeout_sec + 1
62
+ end
63
+ result = nil
64
+ timeout(timeout_sec) do
65
+ queue1.push data
66
+ result = queue2.pop
67
+ end
68
+ process.kill :TERM
69
+ result.should == data
70
+ end
71
+
72
+ it "can pass as many as objects across processes" do
73
+ queue1 = MultiProcessing::Queue.new
74
+ queue2 = MultiProcessing::Queue.new
75
+ data_list = Array.new(1000){|i| "a"*1000 }
76
+ timeout_sec = 10
77
+ # process to echo queue1 -> queue 2
78
+ process = MultiProcessing::Process.new do
79
+ echo_queue = ::Queue.new
80
+ Thread.new do
81
+ loop do
82
+ echo_queue.push queue1.pop
83
+ end
84
+ end
85
+ Thread.new do
86
+ loop do
87
+ queue2.push echo_queue.pop
88
+ end
89
+ end
90
+ sleep timeout_sec + 1
91
+ end
92
+ result = []
93
+ timeout(timeout_sec) do
94
+ Thread.new do
95
+ data_list.each do |data|
96
+ queue1.push data
97
+ end
98
+ end
99
+ data_list.length.times do
100
+ result << queue2.pop
101
+ end
102
+ end
103
+ process.kill :TERM
104
+ result.should == data_list
105
+ end
106
+
107
+ it "can be closed and joined enqueue thread before enqueue" do
108
+ queue = MultiProcessing::Queue.new
109
+ thread = Thread.new { queue.close.join_thread }
110
+ thread.join.should_not be_nil
111
+ end
112
+
113
+ it "can be closed and joined enqueue thread after enqueue" do
114
+ queue = MultiProcessing::Queue.new
115
+ data_list = Array.new(1000){|i| "a"*1000 }
116
+ timeout_sec = 10
117
+ process = MultiProcessing::Process.new do
118
+ data_list.length.times do
119
+ queue.pop
120
+ end
121
+ sleep timeout_sec + 1
122
+ end
123
+ th = Thread.new do
124
+ data_list.each do |data|
125
+ queue.push data
126
+ end
127
+ queue.close.join_thread
128
+ end
129
+ th.join(timeout_sec).should_not be_nil
130
+ process.kill(:TERM)
131
+ end
132
+
133
+ it "cannot be joined before being closed" do
134
+ queue = MultiProcessing::Queue.new
135
+ proc{ queue.join_thread }.should raise_error MultiProcessing::QueueError
136
+ end
137
+
138
+ end
139
+
140
+
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'timeout'
3
+
4
+ describe MultiProcessing::Semaphore, "initialized with 1" do
5
+
6
+ before do
7
+ @semaphore = MultiProcessing::Semaphore.new 1
8
+ end
9
+
10
+ it "#P return itself" do
11
+ @semaphore.lock.should === @semaphore
12
+ end
13
+
14
+ it "#V returns itself" do
15
+ @semaphore.lock
16
+ @semaphore.unlock.should === @semaphore
17
+ end
18
+
19
+
20
+ it "#count returns number of remaining resource" do
21
+ @semaphore.count.should == 1
22
+ @semaphore.P
23
+ @semaphore.count.should == 0
24
+ @semaphore.V
25
+ @semaphore.V
26
+ @semaphore.count.should == 2
27
+ end
28
+
29
+ it "#try_P returns true if succeed, and P correctly" do
30
+ @semaphore.try_P.should be_true
31
+ @semaphore.count.should == 0
32
+ end
33
+
34
+ it "#try_P returns false if failed" do
35
+ process = MultiProcessing::Process.new do
36
+ @semaphore.P
37
+ sleep 0.2
38
+ end
39
+ sleep 0.1
40
+ @semaphore.try_P.should be_false
41
+ end
42
+
43
+ it "#synchronize can lock resource and yield and free" do
44
+ tmp = nil
45
+ @semaphore.synchronize do
46
+ @semaphore.count.should == 0
47
+ tmp = true
48
+ end
49
+ tmp.should be_true
50
+ @semaphore.count.should == 1
51
+ end
52
+
53
+
54
+
55
+ end
56
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'multiprocessing'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multiprocessing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - clicube
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Classes for inter-process synchronization/communication like thread library
15
+ in ruby standard library
16
+ email:
17
+ - clicube@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/multiprocessing.rb
28
+ - lib/multiprocessing/conditionvariable.rb
29
+ - lib/multiprocessing/externalobject.rb
30
+ - lib/multiprocessing/mutex.rb
31
+ - lib/multiprocessing/namedpipe.rb
32
+ - lib/multiprocessing/process.rb
33
+ - lib/multiprocessing/processerror.rb
34
+ - lib/multiprocessing/queue.rb
35
+ - lib/multiprocessing/semaphore.rb
36
+ - lib/multiprocessing/version.rb
37
+ - multiprocessing.gemspec
38
+ - spec/multiprocessing/mutex_spec.rb
39
+ - spec/multiprocessing/process_spec.rb
40
+ - spec/multiprocessing/queue_spec.rb
41
+ - spec/multiprocessing/semaphore_spec.rb
42
+ - spec/spec_helper.rb
43
+ homepage: https://github.com/clicube/multiprocessing
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Inter-process synchronization/communication
67
+ test_files:
68
+ - spec/multiprocessing/mutex_spec.rb
69
+ - spec/multiprocessing/process_spec.rb
70
+ - spec/multiprocessing/queue_spec.rb
71
+ - spec/multiprocessing/semaphore_spec.rb
72
+ - spec/spec_helper.rb
73
+ has_rdoc: