[Lua] Multi threading Lua...

Started by
24 comments, last by ZealGamedev 13 years, 5 months ago
I have been seeking help on the official Lua forums, but not many people post there, so I thought I would try my luck here. To recap my problem -

I have two C threads, and two lua_States (created via lua_newthread(mMainLuaState)). I have a function setup that ensures correct data parallelism, so in theory thread1 and thread2 should be able to both call the function at the same time (the two calls have their own environment, and only read static read only globals). However, I am getting one crash after another (no helpful information provided in the logs sorry).

Check out my original thread for more details and example code...

http://forum.luahub.com/index.php?topic=2596.0

There is no way I am the first person to try this... somebody out there must know what I am 'missing'?

*fyi I have NOT defined lua_lock/unlock, but for what I am trying to do, I should not have to right?

*here is the example code to save you from having to check out the other thread...

Lua
-- Create a unique message buffer for each threadmsgs2 = {}msgs3 = {}-- Declare two unique functions for each threadfunction foo2() table.insert(msgs2, 777); endfunction foo3() table.insert(msgs3, 777); end


C
	mBarrier = new boost::barrier(3);	// The main lua state is initialize (mLua), create two 'sub states'	mLua2 = lua_newthread(mLua);	assert(lua_type(mLua, -1) == LUA_TTHREAD);	int lua2Ref = luaL_ref(mLua, LUA_REGISTRYINDEX);	assert(lua_type(mLua, -1) != LUA_TTHREAD);	mLua3 = lua_newthread(mLua);	assert(lua_type(mLua, -1) == LUA_TTHREAD);	int lua3Ref = luaL_ref(mLua, LUA_REGISTRYINDEX);	assert(lua_type(mLua, -1) != LUA_TTHREAD);	// Spawn two threads which use mLua2/mLua3 to call foo2/foo3	boost::thread thread2(boost::bind(&Sim::lua2Task, this));	boost::thread thread3(boost::bind(&Sim::lua3Task, this));	mBarrier->wait();


void call(lua_State* lua, std::string& func){	lua_getglobal(lua, func.c_str()); assert(lua_type(lua, -1) == LUA_TFUNCTION);	if (lua_pcall(lua, 0, 0, 0)) Aux::log(lua_tostring(lua, -1));}void Sim::lua2Task(void){	for (int i = 0; i < 10000; ++i)		call(mLua2, std::string("foo2"));	mBarrier->wait();}void Sim::lua3Task(void){	for (int i = 0; i < 10000; ++i)		call(mLua3, std::string("foo3"));	mBarrier->wait();}
Advertisement
Quote:Original post by ZealGamedev
I have two C threads, and two lua_States (created via lua_newthread(mMainLuaState)). I have a function setup that ensures correct data parallelism, so in theory thread1 and thread2 should be able to both call the function at the same time (the two calls have their own environment, and only read static read only globals). However, I am getting one crash after another (no helpful information provided in the logs sorry).
Lua is not thread-safe. If two lua_States share the same global state (as occurs when you use lua_newthread) then you cannot use them concurrently from different C threads.

Incidentally, there is no "official Lua forum". Most users post on the lua-l mailing list.
So youre saying I AM the first person to ever try and do this? Nobody has ever tried to do a simple data parallel task with Lua? That seems VERY strange considering how popular Lua is in the games industry.

Somebody else out there MUST have figured out a way to do this...
Quote:Original post by ZealGamedev
So youre saying I AM the first person to ever try and do this?
No. Where did I say that?

It's perfectly okay to multithread Lua as long as the states are completely separate (created with lua_newstate), and this is commonly done. If you need a more complete solution, Lua Lanes wraps the thread handling process and enables communication between the states, but of course you already know about Lua Lanes, since it's the very first google result for "lua multithreading".
From http://lua-users.org/wiki/ThreadsTutorial

Quote:
Each thread in C which interacts with Lua will need its own Lua state. Each of these states has its own runtime stack. When a new C thread is started, you can create its Lua state in one of two ways. One way is to call lua_open. This creates a new state which is independent of the states in other threads. In this case, you'll need to initialize the Lua state (for example, loading libraries) as if it was the state of a new program. This approach eliminates the need for mutex locks (discussed below), but will keep the threads from sharing global data.

The other approach is to call lua_newthread. This creates a child state which has its own stack and which has access to global data. This approach is discussed here.


That sure seems to imply that two c threads can run in parallel and access global data. The article goes on to talk about implementing the lua_lock/unlock defines... however I really want to avoid locks if possible. I should have the option to avoid locks if I 'promise' lua that two threads will never step on each others data, right?

Perhaps I need to simply set a unique environment (lua_setfenv()) for each thread/function call? Shouldnt that give each thread its own global space, and then I can pass in any global data from the main environment as read only data?
Dude, you can bargain with it all you want, you're not going to turn Lua inherently thread-safe. For the sake of GC, string interning, upvalues, etc., threads write to the global_State quite frequently, even when you aren't operating on what you think of as "global data". So yes, to do this you need locks. [See the rest of the page you quoted for details about this.] But that'll seriously hamstring performance, because of the frequency of locking and the amount of expensive operations performed while the lock is held. (It's better in 5.1 than in earlier versions, but it's still not good.) This is why people who need preemptive threading in Lua tend to use separate global states.
Quote:Dude, you can bargain with it all you want, you're not going to turn Lua inherently thread-safe


Dude, I am not trying to make Lua "thread-safe". I am trying to do some simple data parallelism.

Quote:
threads write to the global_State quite frequently, even when you aren't operating on what you think of as "global data".


Can you give an example? Can you point to where Lua is operating on the "global_State" in the example I posted? Unless there is something funky going on when allocating memory for tables, the two functions should never require any shared "global_State".

Quote:...This is why people who need preemptive threading in Lua tend to use separate global states.


Good thing I am not trying to do preemptive threading eh?

Look, while I appreciate your optimism, I dont think you quite understand what I am trying to do here. Imagine each Lua function is a fragment shader. Each function will be given only thread safe (read only for example) input to perform its task, and will write its output to some unique area of memory. Once all functions have been called, the threads synchronize, so the main thread can process the output.
Quote:Original post by ZealGamedev
Can you give an example? Can you point to where Lua is operating on the "global_State" in the example I posted?
Sure thing. You're calling table.insert, which internally invokes lua_rawset. This changes the size of the table, resulting in the memory counters in the global_State being incremented. Non-atomically. The global lookup of msgs2 probably also puts something on the grey-list in the global_State. (EDIT: and that's assuming it doesn't happen to run an incremental collection, which'll chew on a lot more than that.)

Quote:
Quote:...This is why people who need preemptive threading in Lua tend to use separate global states.


Good thing I am not trying to do preemptive threading eh?
Of course you are trying to do preemptive threading. That's what OS-level threading is. It's preemptive. If it wasn't preemptive, it'd be coroutines. The OS preempts a thread after its timeslice is over, in order to run a different thread. This preemption can create problems if the thread has left state which is visible to other threads in an invalid state. That's why threading is difficult. Because it's preemptive.

Seriously, do me a favor. Walk through the entire execution of a call to lua_newtable in your debugger, descending into every function call. I think it'll be eye-opening for you.
Quote:
You're calling table.insert, which internally invokes lua_rawset. This changes the size of the table, resulting in the memory counters in the global_State being incremented. Non-atomically


Now were getting somewhere. So youre saying that even when I manipulate a data structure that is local to a functions environment, it still writes to global "memory counters"? Doesnt that strike you as kind of bad design? I see no reason why allocating local data needs to involve the global state (aside from garbage collection, but that could be solved in other creative ways).

Quote:
Of course you are trying to do preemptive threading.


What I meant is I am not yielding threads manually. I am running one thread per hardware core, and my task manager runs them cooperatively until all tasks are complete.
Quote:Original post by ZealGamedev
I see no reason why allocating local data needs to involve the global state (aside from garbage collection, but that could be solved in other creative ways).
Spend some time thinking about string interning and cross-coroutine upvalues, and you'll get a better idea as to why it's necessary. As for garbage collection, if you want to try implementing a thread-safe, lock-free, incremental generational garbage collector, well.... it would be a very useful thing to have, for Lua and for pretty much any other language out there.
Quote:What I meant is I am not yielding threads manually. I am running one thread per hardware core, and my task manager runs them cooperatively until all tasks are complete.
That's what makes it preemptive: The lack of yielding threads manually.

This topic is closed to new replies.

Advertisement