Sinatra内でloopなど定期作業を行うには

単純に Thread.start でスレッドを分けるんですけどね。重要なのはいつものイディオムだと問題が生じる、ということ。
ひとまず結論を先に示します。

ラシックスタイル

require 'sinatra'

Thread.start do
  loop do
    puts Time.now
    sleep 3
  end
end

get '/update' do
  "update."
end
get '/update2' do
  "update2!"
end

モジュラースタイル

require 'sinatra/base'

Thread.start do
  loop do
    puts Time.now
    sleep 3
  end
end

class App < Sinatra::Base
  configure do
    enable :logging
  end
  
  get '/update' do
    "update."
  end
  get '/update2' do
    "update2!"
  end
  run! if app_file == $0
end

個人的感想(というかポイント

Thread を使う場合には

th = []
n.times do |i|
  th[i] = Thread.start { do_something }
end
th.each { |thread| thread.join }

Thread.joinのイディオム - 別館 子子子子子子(ねこのここねこ)

のようにThread.start したインスタンスを集めてそのあとで Thread#join する、のがイディオムになってます。
なので当然のように Thread#join を書きたくなるのですが、ここではそれを書くとドツボに落ちます。なぜなら、Thread#join しなきゃいけないのはそれが無いとメインスレッドが終了してしまうからです。逆に、Sinatra ではメインスレッドは終了していないため、Thread#join すると問題が生じるのです。
それに気付くまでにかなり時間を要してしまいました…orz