当前位置: 首页 > 面试题库 >

在Rails线程中访问变量

国斌斌
2023-03-14
问题内容

我正在为基于Web的幻灯片显示构建应用程序,其中一个“主”用户可以在幻灯片之间移动,每个人的浏览器都可以跟随。为此,我使用websockets和Redis作为全局通道来发送消息。每个连接的客户端都将信息存储在数组中@clients。然后,我有一个单独的线程用于订阅Redis通道,其中定义了一个“
on.message”块,该块应将消息发送给@clients数组中的每个人,但是该数组在此块内为空(在该块中的其他任何地方都不为空)模块)。

大致遵循此示例:https :
//devcenter.heroku.com/articles/ruby-
websockets

相关代码位于自定义中间件类中:

require 'faye/websocket'
require 'redis'

class WsCommunication
  KEEPALIVE_TIME = 15 #seconds
  CHANNEL = 'vip-deck'

  def initialize(app)
    @app = app
    @clients = []

    uri = URI.parse(ENV['REDISCLOUD_URL'])
    Thread.new do
      redis_sub = Redis.new(host: uri.host, port: uri.port, password: uri.password)
      redis_sub.subscribe(CHANNEL) do |on|
        on.message do |channel, msg|
          puts @clients.count
          ### prints '0,' no clients receive msg
          @clients.each { |ws| ws.send(msg) }
        end
      end
    end
  end

  def call(env)
    if Faye::WebSocket.websocket?(env)
    ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME})

    ws.on :open do |event|
      @clients << ws
      puts @clients.count
      ### prints actual number of clients
    end

    ws.on :message do |event|
      $redis.publish(CHANNEL, event.data)
    end

    ws.on :close do |event|
      @clients.delete(ws)
      ws = nil
    end

    ws.rack_response
  else
    @app.call(env)
  end
end
end

@clients在新线程内部访问数组时是否为空,因为实例变量不跨线程共享?如果可以,如何在线程之间共享变量?

我也尝试使用$ clients(全局变量,应该可以跨线程访问),但无济于事。

编辑:男人这个站点是如此的充满了抢夺者。没有人再回答了,他们只是做了些微修改以获得代表。


问题答案:

在末尾更新编辑:显示工作代码。除调试代码外,未修改主模块。注意:我确实遇到了我已经提到的有关终止之前需要退订的问题。

该代码看起来正确。我想看看您如何实例化它。

在config / application.rb中,您可能至少有类似以下内容:

require 'ws_communication'
config.middleware.use WsCommunication

然后,在您的JavaScript客户端中,您应该具有以下内容:

var ws = new WebSocket(uri);

您是否实例化WsCommunication的另一个实例?这会将@clients设置为一个空数组,并且可能会出现您的症状。这样的事情是不正确的:

var ws = new WsCommunication;

如果您显示客户端,可能会对我们有所帮助,如果此帖子对您没有帮助,也可能对config / application.rb有所帮助。

顺便说一句,我同意这样的意见,即@clients在任何更新上都应该由互斥体保护,即使不能读取也是如此。这是一个动态结构,可以在事件驱动的系统中随时更改。
redis-mutex是一个不错的选择。(希望该链接是正确的,因为目前Github似乎在所有内容上抛出500个错误。)

您可能还注意到$ redis.publish返回接收消息的客户端数量的整数值。

最后,您可能会发现需要在终止之前确保取消订阅频道。我遇到过这样的情况,由于没有清除同一频道的早期订阅,我最终多次,甚至多次发送每条消息。由于您正在订阅一个线程中的通道,因此您将需要在同一线程中退订,否则进程将“挂起”,等待正确的线程神奇地出现。我通过设置“取消订阅”标志然后发送消息来处理这种情况。然后,在on.message块中,我测试取消订阅标志并在那里发出取消订阅。

您提供的模块,仅进行了少量调试修改:

require 'faye/websocket'
require 'redis'

class WsCommunication
  KEEPALIVE_TIME = 15 #seconds
  CHANNEL = 'vip-deck'

  def initialize(app)
    @app = app
    @clients = []
    uri = URI.parse(ENV['REDISCLOUD_URL'])
    $redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)
    Thread.new do
      redis_sub = Redis.new(host: uri.host, port: uri.port, password: uri.password)
      redis_sub.subscribe(CHANNEL) do |on|
        on.message do |channel, msg|
          puts "Message event. Clients receiving:#{@clients.count};"
          @clients.each { |ws| ws.send(msg) }
        end
      end
    end
  end

  def call(env)
    if Faye::WebSocket.websocket?(env)
      ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME})

      ws.on :open do |event|
        @clients << ws
        puts "Open event. Clients open:#{@clients.count};"
      end

      ws.on :message do |event|
        receivers = $redis.publish(CHANNEL, event.data)
        puts "Message published:#{event.data}; Receivers:#{receivers};"
      end

      ws.on :close do |event|
        @clients.delete(ws)
        puts "Close event. Clients open:#{@clients.count};"
        ws = nil
      end

      ws.rack_response
    else
      @app.call(env)
    end
  end
end

我提供的测试订户代码:

# encoding: UTF-8
puts "Starting client-subscriber.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'websocket-client-simple'

puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"

url = ARGV.shift || 'ws://localhost:3000'

EM.run do

  ws = WebSocket::Client::Simple.connect url

  ws.on :message do |msg|
    puts msg
  end

  ws.on :open do
    puts "-- Subscriber open (#{ws.url})"
  end

  ws.on :close do |e|
    puts "-- Subscriber close (#{e.inspect})"
    exit 1
  end

  ws.on :error do |e|
    puts "-- Subscriber error (#{e.inspect})"
  end

end

我提供的测试发布者代码。发布者和订阅者可以很容易地合并,因为这只是测试:

# encoding: UTF-8
puts "Starting client-publisher.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'json'
require 'websocket-client-simple'

puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"

url = ARGV.shift || 'ws://localhost:3000'

EM.run do
  count ||= 0
  timer = EventMachine.add_periodic_timer(5+rand(5)) do
    count += 1
    send({"MESSAGE": "COUNT:#{count};"})
  end

  @ws = WebSocket::Client::Simple.connect url

  @ws.on :message do |msg|
    puts msg
  end

  @ws.on :open do
    puts "-- Publisher open"
  end

  @ws.on :close do |e|
    puts "-- Publisher close (#{e.inspect})"
    exit 1
  end

  @ws.on :error do |e|
    puts "-- Publisher error (#{e.inspect})"
    @ws.close
  end

  def self.send message
    payload = message.is_a?(Hash) ? message : {payload: message}
    @ws.send(payload.to_json)
  end
end

在机架中间件层运行所有这些的示例config.ru:

require './controllers/main'
require './middlewares/ws_communication'
use WsCommunication
run Main.new

这是主要的。我从运行版本中剥离了它,因此如果您使用它,可能需要对其进行调整:

%w(rubygems bundler sinatra/base json erb).each { |m| require m }
ENV['RACK_ENV'] ||= 'development'
Bundler.require
$: << File.expand_path('../', __FILE__)
$: << File.expand_path('../lib', __FILE__)

Dir["./lib/*.rb", "./lib/**/*.rb"].each { |file| require file }
env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']

  class Main < Sinatra::Base

    env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']
    get "/" do
      erb :"index.html"
    end

    get "/assets/js/application.js" do
      content_type :js
      @scheme = env == "production" ? "wss://" : "ws://"
      erb :"application.js"
    end
  end


 类似资料:
  • 我有一个很大的json文件,其中包含一个很长的信息列表,我需要在许多子线程中只读该列表。 在java中,我们可以只通过值而不是引用来传递变量,我希望在内存/磁盘使用方面尽可能减少我的程序。 现在我将完整的列表或它的子列表传递给我创建的每个线程。 有没有一种方法可以从所有线程访问相同的列表变量,而不将完整的列表复制到每个线程中? 我需要“只读取”列表 下面是我的程序是如何工作的 1-服务(等待文件创

  • 如何从多个线程同时访问静态变量。 如果我有这样的课 我需要访问线程1的值,比如 从线程2中,我需要设置如下值 这会导致内存冲突吗?。如果是,建议使用什么方法来处理这种情况?。

  • 我试图制作一个图形用户界面,不断绘制从微处理器接收的信号。我试图通过只使用类来实现这一点,但是失败了,因为只有GUI类是oppend。现在我已经实现了线程(或者至少我认为我有!?)但是每个线程只运行一次。这让我相信我不明白tkinter中的主循环是如何工作的,所以我可以重新编写我的代码,让线程变得活跃吗? 我希望你们中的一些人能帮助我把这段代码变成一个实时更新绘图的程序。 但是它仍然没有更新plo

  • 在线访问者页涵盖 [访问者状况] 和 [在线访问者] 详细信息 1.访问者状况 直接明了的展示三个时间段(10分钟、15分钟、30分钟)内访问者的响应次数,该功能对举办活动的活动页作用较大 2.在线访问者 1)显示在线访问者的详细信息,包含上站时间、最后响应时间、已浏览网页数 2)可以详细知道某个访问者对网站内容的浏览程度 3)同时具有筛选功能,可选择在线、离线、全部及指定IP追踪进行精准筛

  • 我有一个单人班: 在我的应用程序中,可能有多个线程使用这个类来,然后添加和删除student。为了使访问(特别是对实例的访问)是线程安全的,我考虑对方法使用关键字,例如: 但是我认为我的微不足道的更改只能确保在多线程环境中只创建一个实例。我还需要做什么才能使多个线程访问实例的线程安全。如有任何好的建议或意见,敬请接受,谢谢!

  • 问题内容: 我有两个变量,范围是: 现在,由于我的应用程序的大小开始增加,因此我决定将网站的每个模块放入其自己的程序包中,就像子目录一样: 我该如何解决从其他软件包访问和全局变量的问题?这是错误的做法吗?我有一种感觉。 在这种情况下,我怎么会在声明自己的命名空间功能,所以我没有发疯具有固定其名称和所有的时间。 问题答案: 大写的变量名导出为其他包访问,因此并会工作。但是,一般不建议使用子包进行名称