Threads in 0.9.0?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
OmarShehata
Party member
Posts: 259
Joined: Tue May 29, 2012 6:46 pm
Location: Egypt
Contact:

Threads in 0.9.0?

Post by OmarShehata »

I'm having some trouble understanding how threads/channels work in 0.9.0

So I get that there's the Channel API to communicate with threads, but I don't get how to "link" a channel with a thread.

I create a new thread with http://www.love2d.org/wiki/love.thread.newThread, and a new channel with http://www.love2d.org/wiki/love.thread.newChannel or getChannel...and then what? How do I tell it that Channel belongs to that thread..? Any examples on how to use it?
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Threads in 0.9.0?

Post by bartbes »

There is no direct relationship between channels and threads. Channels are basically thread-safe, shared queues. There are two kinds of channels, with subtly different mechanics.

Named channels: These keep their contents even without references, and are available globally, using getChannel. They also get created when they don't exist yet, so just calling getChannel with a non-existent name gives you a fresh one. (Technically, they are also destructed when they are empty, and there are no references, but that's not important.)

Unnamed channels: These get collected normally, and you can only obtain them from newChannel once. If you want to use it in multiple places, you need to find a way to get them there, like the new parameters to [wiki]Thread:start[/wiki], or sending them over another channel.

Of course this doesn't mean you can't pass named channels around, it's just that there's a central "registry" for them.

Code: Select all

-- main
t = love.thread.newThread("thread.lua")
c1 = love.thread.newChannel()
c2 = love.thread.getChannel("cookie")
t:start(c1)
c2:supply(c1:demand())

-- thread
c1 = ...
c2 = love.thread.getChannel("cookie")
c1:supply("Hello, world!")
c2:push(c1:demand())
User avatar
OmarShehata
Party member
Posts: 259
Joined: Tue May 29, 2012 6:46 pm
Location: Egypt
Contact:

Re: Threads in 0.9.0?

Post by OmarShehata »

Thanks for the quick reply bartbes! I just got it to work.
Germanunkol
Party member
Posts: 712
Joined: Fri Jun 22, 2012 4:54 pm
Contact:

Re: Threads in 0.9.0?

Post by Germanunkol »

bartbes wrote:

Code: Select all

-- main
t = love.thread.newThread("thread.lua")
c1 = love.thread.newChannel()
c2 = love.thread.getChannel("cookie")
t:start(c1)
c2:supply(c1:demand())

-- thread
c1 = ...
c2 = love.thread.getChannel("cookie")
c1:supply("Hello, world!")
c2:push(c1:demand())
This looks really difficult compared to the old thread:send, thread:demand and thread:get methods..
Can you name some advantages? Why was the API changed so much?
trAInsported - Write AI to control your trains
Bandana (Dev blog) - Platformer featuring an awesome little ninja by Micha and me
GridCars - Our jam entry for LD31
Germanunkol.de
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Threads in 0.9.0?

Post by bartbes »

That's a convoluted example, it's not harder, and in fact has way less edge cases and gotchas.
User avatar
Helvecta
Party member
Posts: 167
Joined: Wed Sep 26, 2012 6:35 pm

Re: Threads in 0.9.0?

Post by Helvecta »

Allow me to revive this thread to throw into the hat the not-so-convoluted example of how to use love.thread:

Code: Select all

-- main.lua
function love.load()
	thread = love.thread.newThread("thread.lua")
	channel = love.thread.getChannel("test")
	thread:start()
	i = {}
end
function love.update(dt)
	v = channel:pop()
	if v then
		table.insert(i, v)
	end
end
function love.draw()
	love.graphics.print(tostring(i[1]), 10, 10)
end


-- thread.lua
c = love.thread.getChannel("test")

c:push("hi")
Does it look pretty? Eh, maybe not.

Does it work? You bet your muffins. :)
"Bump." -CMFIend420
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Threads in 0.9.0?

Post by s-ol »

Is there a preferred way to implement locks? I think blocking with :demand() on a named channel should do the trick but maybe there's something better I'm not aware of.

Code: Select all

-- create Lock
lock = love.thread.getChannel( lockname )
lock:push(-1) -- message = free

-- obtain lock
lock:demand() -- pop the free message or wait for it

-- do something

-- release lock
lock:push(-1)
It would be nice to save a thread-specific value in the lock that identifies the thread that obtained the lock though.

Edit: more questions:

2. :demand() is implemented in "smart" C++ (vs. long polling), right?
3. Can I somehow build an atomic "tryDemand" method?
If I want to synchronize two threads that work on one array-table of objects, I would write something like this:

Code: Select all

for _,v in ipairs( sharedtable ) do
  if sharedtable.lock:getCount() > 0 then
    sharedtable.lock:demand() -- obtain
    -- do stuff
    sharedtable.lock:push(-1) -- release 
  end
end
However this is not thread safe, both threads could check getCount() at the same time, then one of them obtains the lock whilst the other thread is blocked for the whole update, after which it proceeds to update the same object a second time. The double-updating can be prevented with a simple flag, but this still wastes quite some time, especially if the list is long and this happens quite often.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Threads in 0.9.0?

Post by s-ol »

And here we go, found a possible answer by myself (3.):
Non-blocking locks can be done like this:

Code: Select all

-- in the "scheduler" thread7
update = not (update or true)

-- each thread:
for _,v in ipairs(sharedtable) do
  if v.lock:pop() ~= update then    -- if the lock has been obtained this returns nil, if it has already been updated it returns update 
    -- do something
  end
  l.lock:push( update ) -- flag object as done
end
Aaaaand the first actual problem: If I may only supply flat tables (via Thread.run and channels), how do I make threads cooperatively work on a list of entities?

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
Azhukar
Party member
Posts: 478
Joined: Fri Oct 26, 2012 11:54 am

Re: Threads in 0.9.0?

Post by Azhukar »

S0lll0s wrote:how do I make threads cooperatively work on a list of entities?
I believe the limit for number of channels is pretty high. :awesome:

Would be pretty hilarious to send a flat table full of channels, each channel representing a single table index.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Threads in 0.9.0?

Post by s-ol »

Azhukar wrote:
S0lll0s wrote:how do I make threads cooperatively work on a list of entities?
I believe the limit for number of channels is pretty high. :awesome:

Would be pretty hilarious to send a flat table full of channels, each channel representing a single table index.
that could actually.... work xD

Code: Select all

local meta = love.thread.getChannel("meta")
local keys = {}
for k,v in pairs( ent ) do
  love.thread.getChannel(k):push(v)
  keys[#keys+1] = k
end
meta:supply( keys )
for _,k in ipairs( keys ) do
  love.thread.getChannel(k):clear()
end
Having a way to share actual tables would still be much better. And I believe every OS has a method to share RAM between processes (or at least threads)

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
Post Reply

Who is online

Users browsing this forum: No registered users and 54 guests