I started on Ruby less than a week ago but have already come to
appreciate the power of the language. I am trying my hands on a classic
producer-consumer problem, implemented as an Orange tree (c.f.
http://pine.fm/LearnToProgram/?Chapter=09). The Orange tree grows each
year until it dies and produces a random number of Oranges each year
(Producer). Oranges can be picked as long there are any on the tree
(Consumer).
I've got two problems here:
The following code gives me the following exception (can't attach, no option):
/Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:
warning: instance variable #orange_tree not initialized
/Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:84:in `':
undefined method `age' for nil:NilClass (NoMethodError) from
/Users/Abhijit/Workspace/eclipse/ruby/learn_to_program/orange_tree.rb:45:in `'
I am not sure that the multithreading part is correctly coded.
I've got myself a couple of books, including "Programming Ruby" and "The Ruby Programming Language", but none of them contain a true "producer-consumer problem".
P.S: For the sake of full disclosure, I've also posted this question in the Ruby forum. However, I have seen excellent answers and/or suggestions provided here and hope that I'd get some of those too.
require 'thread'
class OrangeTree
GROWTH_PER_YEAR = 1
AGE_TO_START_PRODUCING_ORANGE = 3
AGE_TO_DIE = 7
ORANGE_COUNT_RELATIVE_TO_AGE = 50
def initialize
#height = 0
#age = 0
#orange_count = 0
end
def height
return #height
end
def age
return #age
end
def count_the_oranges
return #orange_count
end
def one_year_passes
#age += 1
#height += GROWTH_PER_YEAR
#orange_count = Math.rand(#age..AGE_TO_DIE) * Math.log(#age) * ORANGE_COUNT_RELATIVE_TO_AGE
end
def pick_an_orange
if (#age == AGE_TO_DIE)
puts "Sorry, the Orange tree is dead"
elsif (#orange_count > 0)
#orange_count -= 1
puts "The Orange is delicious"
else
puts "Sorry, no Oranges to pick"
end
end
end
class Worker
def initialize(mutex, cv, orange_tree)
#mutex = mutex
#cv = cv
#orange_tree = orange_tree
end
def do_some_work
Thread.new do
until (#orange_tree.age == OrangeTree.AGE_TO_DIE)
#mutex.synchronize do
sleep_time = rand(0..5)
puts "Orange picker going to sleep for #{sleep_time}"
sleep(sleep_time)
puts "Orange picker woke up after sleeping for #{sleep_time}"
#orange_tree.pick_an_orange
puts "Orange picker waiting patiently..."
#cv.wait(#mutex)
end
end
end
Thread.new do
until (#orange_tree.age == OrangeTree.AGE_TO_DIE)
#mutex.synchronize do
sleep_time = rand(0..5)
puts "Age increaser going to sleep for #{sleep_time}"
sleep(sleep_time)
puts "Age increaser woke up after sleeping for #{sleep_time}"
#orange_tree.one_year_passes
puts "Age increaser increased the age"
#cv.signal
end
end
end
end
Worker.new(Mutex.new, ConditionVariable.new, OrangeTree.new).do_some_work
until (#orange_tree.age == OrangeTree.AGE_TO_DIE)
# wait for the Threads to finish
end
end
#orange_tree is an instance variable of the Worker object and may only be accessed from inside the object. You're trying to access it from the global scope in your "until" condition, where it doesn't exist. There are a few ways to address it, but this one requires the fewest changes:
...
orange_tree = OrangeTree.new
Worker.new(Mutex.new, ConditionVariable.new, orange_tree).do_some_work
until (orange_tree.age == OrangeTree::AGE_TO_DIE)
...
You should also look into Thread#join: http://www.ruby-doc.org/core-1.9.3/Thread.html#method-i-join. It it will take care of the waiting for you. The rest of the multithreading code looks technically correct. I'm sure you know that if this were more than an exercise, you would want to make the mutexes more granular. As they are now the two threads will execute nearly mutually exclusively which kind of defeats the point of threads.
Also, look up attr_accessor or attr_reader. They will save you having to manually define OrangeTree#height, age, etc.
Multiple join example
threads = []
threads << Thread.new do
...
end
threads << Threads.new do
...
end
threads.each { |thread| thread.join }
Related
I'm new to multi-threading and I'm looking for some help understanding the idiomatic way of doing something when a thread is finished, such as updating a progress bar. In the following example, I have several lists of items and routines to do some "parsing" of each item. I plan to have a progress bar for each list so I'd like to be able to have each list's parsing routine update the percentage of items completed. The only "trigger" point I see is at the puts statement at the end of an item's sleepy method (the method being threaded). What's the generally accepted strategy for capturing the completion, especially when the scope of the action is outside the method running in the thread?
Thanks!
# frozen_string_literal: true
require 'concurrent'
$stdout.sync = true
class TheList
attr_reader :items
def initialize(list_id, n_items)
#id = list_id
#items = []
n_items.times { |n| #items << Item.new(#id, n) }
end
def parse_list(pool)
#items.each do |item|
pool.post { item.sleepy(rand(3..8)) }
end
end
end
class Item
attr_reader :id
def initialize (list_id, item_id)
#id = item_id
#list_id = list_id
end
def sleepy(seconds)
sleep(seconds)
# This puts statement signifies the end of the method threaded
puts "List ID: #{#list_id} item ID:#{#id} slept for #{seconds} seconds"
end
end
lists = []
5.times do |i|
lists << TheList.new(i, rand(5..10))
end
pool = Concurrent::FixedThreadPool.new(Concurrent.processor_count)
lists.each do |list|
list.parse_list(pool)
end
pool.shutdown
pool.wait_for_termination
The issue isn't really about "knowing when the thread finished", but rather, how can you update a shared progress bar without race conditions.
To explain the problem: say you had a central ThreadList#progress_var variable, and as the last line of each thread, you incremented it with +=. This would introduce a race condition because two threads can perform the operation at the same time (and could overwrite each other's results).
To get around this, the typical approach is to use a Mutex which is an essential concept to understand if you're learning multithreading.
The actual implementation isn't that difficult:
require 'mutex'
class ThreadList
def initialize
#semaphore = Mutex.new
#progress_bar = 0
end
def increment_progress_bar(amount)
#semaphore.synchronize do
#progress_bar += amount
end
end
end
Because of that #semaphore.synchronize block, you can now safely call this increment_progress_bar method from threads, without the risk of race condition.
As a beginner learning to program, it is extremely helpful to have such a supportive community out there!
I am having trouble getting this 'sample' game working. I am trying to develop a battle system where the player comes across opponents as they progress through a number of rooms. For some reason, when I run it on command prompt, it simply displays "you died" then exits. I am not sure where to go from here.
Any help would be greatly appreciated.
class Player
attr_accessor :hit_points, :attack_power
def initialize(hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= Opponent.attack_power
end
def print_status
puts "*" * 80
puts "HP: #{hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Death
puts "You died"
exit(1)
end
class Opponent
def initialize (hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
puts "you come across this awful opponent"
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= player.attack_power
end
def interact(player)
while player.alive?
hurt
break if #hit_points < 1
alive?
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt #{player_damage_done} damage, killing your opponent."
room
else
death
end
end
end
class Room
puts "you are now in the scary room, and you see an opponent!"
puts "You come accross a weaker opponent. It is a fish."
puts "Do you want to (F)ight or (L)eave?"
action = $stdin.gets.chomp
if action.downcase == "f"
fish = Opponent.new(2, 1)
fish.interact
else
death
end
end
Player.new(200, 1)
Room.new
class Engine
end
This is breaking because Death is a class and all the code within it is in the body of the class. That means this code will be executed when the class is defined, not at the time that death is called.
You haven't defined a method named death.
Because the Death class is tiny, and it would be awkward to name a method within it that stops the game (Death.death, Death.die, Death.run, Death.execute... Not great), and you don't need any of the advantages of a class (such as multiple instances or attributes stored in instance variables), I suggest you make the death action a part of the Player class.
class Player
# ...
def die
puts "You died"
exit(1)
end
end
Then when you've called death (the currently undefined method) Replace it with player.die.
As noted by #Kennycoc, you'll need to define a method for the death of an enemy, too.
So, it seems like you're under the impression that the code in the top level of the class is run when the class is instantiated (Class.new). This is not the case! Everything in the top level of the class is run as it is defined.
The simplest fix to get this running would be to add all the code in your top level of each class under a method named initialize this is what's run when the class is instantiated.
Also, you're using your Death class as if it were a method. You could either change it from class Death to def death or change your calls to Death.new after moving the code to the initialize method (this is not a normal pattern, but would work).
I am currently trying to implement a battle system within a text-based game that I am writing. The player will go from room to room, and sometimes face multiple opponents.
I would like to:
have the player start off with a max number of hit points, and have that decline as the game progresses
pre-determine the strength (max hit points) of each opponent
have the player face many opponents at a time
This is what I have so far, but I am having a lot of difficulty conceptualizing the interaction between the player and the opponents. Also, how would I have the player face multiple opponents in succession?
Any tips would help quite a lot!
Thanks!
GF
Code:
class Player
attr_accessor :hit_points, :attack_power
def initialize
#hit_points = MAX_HIT_POINTS
#attack_power = rand(2 .. 15)
end
def alive?
#hit_points > 0
end
def hurt
#hit_points -= amount
end
def print_status
puts "*" * 80
puts "HP: #{#hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Opponent
attr_accessor :hit_points, :attack_power
def initialize
#hit_points = MAX_HIT_POINTS
#attack_power = rand(1 .. 10)
end
def alive?
#hit_points > 0
end
def hurt
#hit_points -= amount
end
def interact(player)
player_damage_done = 0
player_damage_taken = 0
while player.alive?
hurt(player.attack_power)
player_damage_done += player.attack_power
break unless alive?
player.hurt(#attack_power)
player_damage_taken += #attack_power
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt # {player_damage_done} damage, killing your opponent."
print "\n"
player.addPoints(player_damage_taken + player_damage_done)
else
print "Your opponent was too powerful and you died."
death
end
end
end
You should probably have some kind of environment class to keep track of all your characters. This could be expanded upon to allow movement, etc. Something super simple could look like this:
class Environment
def initialize(player, baddies)
#player = player
#baddies = baddies
end
def play_game
#baddies.each do |baddie|
baddie.interact(#player)
end
end
end
baddies = 3.times.map do
Opponent.new
end
Environment.new(Player.new, baddies).play_game
Also, your code as presented won't work. Your hurt methods are acting like they accept an amount parameter, but you don't ever declare that; you call player.add_points, but don't define that method.. Let me know if you have any other specific questions
I've been trying to test a program that simulates an elevator for two days now with little success. Here's my elevator class, the program is still a work in progress and I've also commented out some methods that might not be essential to the test I'm having trouble with. I'll gladly show more code if you think it's needed
class Elevator
attr_accessor :current_floor
GROUND = 0
TOP = 15
def initialize
#floors = [] # list of floors to travel to
#pending = [] # store floors not in direction of travel
#current_floor = GROUND
#going_up = true # cannot travel downward from ground floor
#going_down = false
end
def get_input
gets.chomp
end
def run
enter_floors
sort_floors
move_to_floor
end
def enter_floors
# prompts the user for input and calls check_floor_numbers
end
def check_floor_numbers floors
# calls validate_floors to ensure user entered '2' instead of 'two'
# if proper floor numbers have been entered this method adds the number
# to #floors array other wise it calls floor_error_message
end
def floor_error_message
puts "Please enter numbers only."
enter_floors
end
def sort_floors
# if we are on the ground floor this method sorts #floors in ascending order
# if we are on the top floor it sorts #floors in descending order
# else it calls check_direction_of_travel
end
def move_to_floor
floor = #floors[0]
if #current_floor == floor
puts "You are already on floor #{floor}"
else
print_direction
(#current_floor..floor).each { |floor| puts "...#{floor}" }
#current_floor = floor # update current_floor
#floors.delete_at(0) # remove floor from list
end
check_for_more_passengers
end
def check_for_more_passengers
puts "Are there any more passengers? (Y/N)"
answer = (get_input).upcase
answer == 'Y' ? run : check_next_move
end
def check_next_move
if #floors.empty? && #pending.empty?
end_ride
else
move_to_floor
end
end
def check_direction_of_travel
# not implemented - add floor to appropriate array depending on dir
# of travel
end
def end_ride
puts "\n\nEND."
end
def print_direction
msg = " "
#going_up ? msg = "Going Up!" : msg = "Going Down!"
puts msg
end
end
I'm trying to test that the elevator can move to a specific floor. At first I was having trouble testing input from the console without running the program itself. I asked a question about this and was referred to this answer in a different question. The answer in question extract gets.chomp to a separate method then overrides the method in the tests. I ended up with something like this:
describe "it can move to a floor" do
before do
##moves = ["2", "N"]
def get_input; ##moves.next end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
Problem: get_input was not properly overidden and running the test suit prompted the user for input so it was suggested that I open the Elevator class in the test itself to ensure that the method was properly overridden. Attempting to do so eventually led to a test like this:
describe "it can move to a floor" do
before do
class Elevator
attr_accessor :current_floor
##moves = ["2", "N"]
def get_input; ##moves.next end
def run; end
end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
I had to override run and add an attr_accessor for current_floor because I was getting method missing errors.
Problem: This test gives the following error:
1) Failure: it can move to a floor#test_0001_should move to floor 2
[elevator_test.rb:24]: Expected: nil Actual: 2
I've tried to tidy up the Elevator class as much as possible and keep the methods as simple as I could given the parameters of the program.
Can anyone point me in the right direction towards getting this solved, with maybe pseudocode examples (if possible) to demonstrate how I should approach this problem if the answer is to refactor.
Please bear in mind that I'd also like to implement other tests like checking that the elevator class can maintain a list of floors, or that it can change direction, in the future when you answer.
Your test class ElevatorTest is redefining the Elevator to override method get_input, but it is not opening the class Elevator defined in elevator.rb, but instead it is sort of creating a new class Elevator which happens to be defined inside the class ElevatorTest. Remember every class is also a module, so now you have a new class ElevatorTest::Elevator.
To fix this issue, I have made some changes to elevator_test.rb which is shown below.
gem 'minitest', '>= 5.0.0'
require 'minitest/spec'
require 'minitest/autorun'
require_relative 'elevator'
class Elevator
##moves = ["2", "N"].each
def get_input; ##moves.next end
end
class ElevatorTest < MiniTest::Test
def test_working
assert_equal(1, 1)
end
describe "it can move to a floor" do
before do
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
end
Also, please remember to use .each while defining ##moves - it returns an enumerator. We can call .next only on an enumerator
I am trying to figure out how to make use of deferrables when it comes to long running computations that I have to implement on my own. For my example I want to calculate the first 200000 Fibonacci numbers but return only a certain one.
My first attempt of a deferrable looked like so:
class FibA
include EM::Deferrable
def calc m, n
fibs = [0,1]
i = 0
do_work = proc{
puts "Deferred Thread: #{Thread.current}"
if i < m
fibs.push(fibs[-1] + fibs[-2])
i += 1
EM.next_tick &do_work
else
self.succeed fibs[n]
end
}
EM.next_tick &do_work
end
end
EM.run do
puts "Main Thread: #{Thread.current}"
puts "#{Time.now.to_i}\n"
EM.add_periodic_timer(1) do
puts "#{Time.now.to_i}\n"
end
# calculating in reactor thread
fib_a = FibA.new
fib_a.callback do |x|
puts "A - Result: #{x}"
EM.stop
end
fib_a.calc(150000, 21)
end
Only to realize that everything seemed to work pretty well, but the thread the deferrable runs in is the same as the reactor thread (knowing that everything runs inside one system thread unless rbx or jruby are used). So I came up with a second attempt that seems nicer to me, especially because of different callback binding mechanism and the use of different threads.
class FibB
include EM::Deferrable
def initialize
#callbacks = []
end
def calc m, n
work = Proc.new do
puts "Deferred Thread: #{Thread.current}"
#fibs = 1.upto(m).inject([0,1]){ |a, v| a.push(a[-1]+a[-2]); a }
end
done = Proc.new do
#callbacks.each{ |cb| cb.call #fibs[n]}
end
EM.defer work, done
end
def on_done &cb
#callbacks << cb
end
end
EM.run do
puts "Main Thread: #{Thread.current}"
puts "#{Time.now.to_i}\n"
EM.add_periodic_timer(1) do
puts "#{Time.now.to_i}\n"
end
# calculating in external thread
fib_b = FibB.new
fib_b.on_done do |res|
puts "B - Result: #{res}"
end
fib_b.on_done do
EM.stop
end
fib_b.calc(150000, 22)
end
Which one is the implementation that I should prefer? Are both wrong? Is there another, a better one?
Even more interesting: Is the second attempts a perfect way to implement whatever I want (except I/O op's) without blocking the reactor?
Definitely EM.defer (or Thread.new I suppose), doing a long-running calculation in EM.next_tick will block your reactor for other things.
As a general rule, you don't want ANY block running inside reactor to be running for long regardless if it is or isn't IO blocking or the entire app halts while this is happening.