Page 1 of 3

Threads in 0.9.0?

Posted: Sun Dec 15, 2013 6:19 pm
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?

Re: Threads in 0.9.0?

Posted: Sun Dec 15, 2013 6:38 pm
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())

Re: Threads in 0.9.0?

Posted: Sun Dec 15, 2013 8:26 pm
by OmarShehata
Thanks for the quick reply bartbes! I just got it to work.

Re: Threads in 0.9.0?

Posted: Sun Dec 15, 2013 10:17 pm
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?

Re: Threads in 0.9.0?

Posted: Sun Dec 15, 2013 10:25 pm
by bartbes
That's a convoluted example, it's not harder, and in fact has way less edge cases and gotchas.

Re: Threads in 0.9.0?

Posted: Wed Jan 08, 2014 7:14 am
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. :)

Re: Threads in 0.9.0?

Posted: Mon Dec 01, 2014 11:19 am
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.

Re: Threads in 0.9.0?

Posted: Mon Dec 01, 2014 11:53 am
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?

Re: Threads in 0.9.0?

Posted: Mon Dec 01, 2014 6:46 pm
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.

Re: Threads in 0.9.0?

Posted: Mon Dec 01, 2014 7:25 pm
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)