Search code examples
rubymultithreadingsynchronize

Ruby synchronize threads around serial


I am sending data via Serial to an Arduino. I want to build error checking, so I don't want to send the next command until the previous has "checked out".

In the pseudo code below, I want to prevent the next command .pop until the process has "released" the next command. Otherwise I want to retry the previous command.

Ideally, the lock would happen just as send starts.

class Arduino
  @threads = []

  def initialize
    # Connect to arduino serial
    @serial

    start_queues
  end

  def start_queues
    #read from serial
    @threads << Thread.new do
      while line = @serial.readline
        process(line)
      end
    end

    #pop from queue to send to Arduino
    @threads << Thread.new do
      loop do
        send(Queue.send_queue.pop)

        # Wait here until previous command completes properly
      end
    end
  end

  def process(data)
    # Check cmd
    # If success, 'send' next command
    # If error, requeue & 'send'
  end


  def send(data)
    @serial.write(data)
  end

end

Solution

  • Using Mutexes and ConditionalVariables I was able to lock the "sending" thread.

    For anyone looking at multithreading, hope this helps:

    class Arduino
    
      def initialize
        @threads    = []
        @work_queue = Queue.new
        @mutex      = Mutex.new
        @cmd_lock   = ConditionVariable.new
        @serial     = Queue.new
    
        # Connect to arduino serial
        start_queues
      end
    
      def start_queues
        #read from serial
        @threads << Thread.new do
          loop do
            process(@serial.pop)
          end
        end
    
        #pop from queue to send to Arduino
        @threads << Thread.new do
          loop do
            @mutex.synchronize do
              send(@work_queue.pop)
    
              @cmd_lock.wait(@mutex)
            end
          end
        end
      end
    
      def process(data)
        puts data
    
        # Check cmd
        if data == "example1"
          @cmd_lock.signal
        end
      end
    
    
      def send(data)
        @serial << data
      end
    
      def go
        puts "GO"
        @work_queue << "example1"
        @work_queue << "example2"
        @work_queue << "example3"
    
        @threads.each(&:join)
      end
    
    end
    
    Arduino.new.go