Threading query

BlitzMax Forums/BlitzMax Programming/Threading query

GfK(Posted 2008) [#1]
How would I go about loading an image (or sound, or some other file) in a separate thread, then pass the data to the main thread for use?

There seems to be functions for 'thread storage' - AllocThreadData, SetThreadData, GetThreadData etc, and I'd assume it to be something to do with this, but the explanation for each doesn't reach to more than four words at best - The help for GetThreadData() simply reads "Get thread storage data", which is no help at all.


ziggy(Posted 2008) [#2]
Just load what you want on the secondary thread, and share the variable on the main thread (it can be a return variable on the called function, you may access using MyThread.GetThreadData once the TThread.Status is done).

Be sure to not be using it unless the loading thread has ended. if using the TThread class, check the status variable to ensure it. Loading images on a secondary thread on OpenGL does not work well sometimes. I've seen no problems on DX.

The data allocation you're talking about is used to pass parameters on the threaded first function call, and it can be ignored most of the times, unless you're making a very speciffic thing.

[EDIT:] I confused GetThreadData with the Result() method. See next post.


GfK(Posted 2008) [#3]
So.... what does GetThreadData actually return? The raw data itself? A pointer? An object?


ziggy(Posted 2008) [#4]
That is a simple example, I hope it'll make it clearer:
Local T:TThread = New TThread

t.Start(LoadImage1, "MyImage.bmp")

While t.Status() <> TThread.STOPPED
	
WEnd

Local MyImage:TImage = TImage(T.Result())

Function LoadImage1:Object(Obj:Object)
	Local MyImage:TImage = LoadImage(String(Obj))
	Return MyImage
End Function


this example is not very functional, as the main thread waits for the secondary thread doing nothing, but it shows how the class works as a starting point.

GetThreadData is something you should not need when using the high level TThread class, wich I would recommend you to use.

[EDIT:] The OpenGL issue seems to happen only when using GL commands from several threads, not when loading images or sound.


xlsior(Posted 2008) [#5]
How would I go about loading an image (or sound, or some other file) in a separate thread, then pass the data to the main thread for use?


I haven't looked into threads yet myself so I can't really answer your question directly, but depending on what you need the data for you can also approach this the other way around:

for example, if you need a loading screen:

1) Spin off your loading screen to a seperate thread which just shows an animated progress bar etc.
2) have the main thread continue loading images
3) when the loading is completed, let the animation thread know to end itself, and have the main thread take over the screen again.

(Unless you're looking to load files in the background while the main game is already in progress of course, in that case the above obviously won't suffice)


ziggy(Posted 2008) [#6]
@xlsior: If I'm not wrong, you can not draw anything to a graphics context from a thread that has not created the graphic context, so you can't do it this way. specially when using OpenGL. DX may work, but may have some flickering sometimes. So theorically your approach should work, but it doesn't becouse the the non thread safeness of GL and DX.


GfK(Posted 2008) [#7]
Ziggy - that helps a lot. Thanks.

But why this:
Function LoadImage1:Object(Obj:Object)
	Local MyImage:TImage = LoadImage(String(Obj))
	Return MyImage
End Function

Instead of this:
Function LoadImage1:TImage(Obj:String)
	Local MyImage:TImage = LoadImage(String(Obj))
	Return MyImage
End Function

??


ziggy(Posted 2008) [#8]
Becouse the signature of the function called by any thread starter has to be:
Name:Object(Var:Object)

Otherwise you'll get: Unable to convert from 'brl.max2d.TImage(String)' to 'Object(Object)'

but you can cast everything inside and outside the function, so not really a major issue.


BlitzSupport(Posted 2008) [#9]
This might possibly be of some help, though it downloads images from the internet while animating, so you'll have to let it go online. (Leave it running a while as it downloads multiple images.)

It's a messy work-in-progress, but I'm only loading Pixmaps outside the main thread, as my understanding is that Images are tied up with graphics contexts (not thread-friendly), while Pixmaps are only blocks of memory. I just convert the resulting Pixmap to an Image once it's returned.




GfK(Posted 2008) [#10]
How comes I don't have a 'tThread' datatype?

[edit] Never mind - didn't realise I had to import pub.thread.


GfK(Posted 2008) [#11]
OK, another query. Why do DetatchThread() and WaitThread() expect an Integer, not a TThread?


ziggy(Posted 2008) [#12]
WaitThread expects an integer that represents the number of millisecs to wait. DetatchThread does not expect an integer but a TThread instance (the numeric object handle is passed to the internal C function, so it is seen by any ide as a integer, but internally it uses the object instance.)


GfK(Posted 2008) [#13]
I see...

Just noticed a file called 'test.exe.log" is being created every time I run my program. It contains one line; "GC Warning: Finalization cycle involving d19258"

What's that about??


ziggy(Posted 2008) [#14]
I have no idea, that is always created on threaded apps, it may be something related to how max handles threads or something


Brucey(Posted 2008) [#15]
It's the new GC framework. It creates that file by default.
I'm sure there's a flag somewhere in there to disable it...


GfK(Posted 2008) [#16]
Hmm... am I better off just not using this stuff?

I can't release a game with it doing that. Even though it works great otherwise.

Another thing - if I compile "mycode.bmx", how come it creates another file called ".mycode.bmx"?


ImaginaryHuman(Posted 2008) [#17]
The AllocThreadData, SetThreadData and GetThreadData are to be used for `Thread Local Storage`, sometimes referred to as TLS.

Basically, when you need to create some storage space which is totally confined to and local to a thread - ie unique to each thread, you can allocate that storage space using AllocThreadData(), you can store some kind of Object in it using SetThreadData, and retrieve it using GetThreadData. I don't think there is any kind of `deallocate thread storage` command so I presume they just pile up for the lifetime of the thread.

It would be basically the same thing as using local variables within the function that the thread is executing. In fact there isn't a lot of benefit from using TLS than just using local variables, except perhaps if you have quite a number of them to keep track of. Using locals will be faster because it doesn't have to go find the data based on a handle.

You start by allocating some storage space local to the thread, by assigning a handle to the return value of AllocThreadData(). e.g. myData:Int=AllocThreadData(). The handle is an integer. Its value is not predictable.

Once you've created the handle, you've made space in the thread's local memory (stack presumably) for storing one piece of information. From what I could tell, each time you call this function to allocate more room it decreases the handle's value by 8 - which suggests you can store anything up to 8-bytes as your data (to support doubles and longs). Not sure about that.

When you have a handle you can then store data in it by saying SetThreadData(MyData,MyStuff) and can later retrieve it from anywhere in the thread's scope of execution using MyStuff=GetThreadData(MyData). Not sure if you need to cast to Object/back to your Type. You should be able to store integers and all the data types as well as custom types.

TLS cannot be used to transfer information between threads. It's Thread LOCAL storage, not thread shared storage. The only way to get access to the stored data is for the accessing function call to occur within the thread that allocated and stored the data. If you want to use it to somehow pass data to another thread, you can't, unless the data that the local thread retrieves is then stored in a globally accessible place afterwards.

One of my gripes about TLS is that in order for you to store and retrieve a piece of data that is local to the thread, you have to have a handle. The handle lives OUTSIDE of thread local storage. You can't really have a handle stored within TLS itself. So then you have the issue of having to store the handle in a local, a field, or a global, or passing it to functions as a parameter. This largely undermines the whole purpose of TLS. If you try to store data in your main function and you want it to be retrievable in some deeply nested function call, you can't just have that nested function say x=GetThreadData(handle) because you have to have access to the handle first, which means you have to share it globally (which means you can't have local handles), or pass it as a parameter, or pass the handle of the type instance containing a field, which means you might as well be passing the value that you stored anyway, so what's the point?! (unless you have lots of data to store, maybe?)

I eventually settled on just passing the one piece of data that I wanted to store in TLS, as a function parameter. Not only is it faster to access than going via an indirect retrieval function, but it was easier to implement.

This of course has nothing to do with how to load images/sounds in a threaded way. I like James's suggestion of loading into a pixmap rather than an image, because that does mean you are only loading memory bytes outside of the gpu/graphics API. LoadImage loads a pixmap first anyway before uploading as a texture.


GfK(Posted 2008) [#18]
So.... anybody know how to get rid of the error/log file?

Its doing my head in now.


jkrankie(Posted 2008) [#19]
Turn Debug off?

Cheers
Charlie


GfK(Posted 2008) [#20]
Nope, tried that.


GfK(Posted 2008) [#21]
Interesting. I get the GC log file error if I load an OGG format sound in a thread - even though the loaded sound is returned to the main thread and played correctly. I don't get the error if I load an image in a thread (which is pointless, since that's a fast process anyway).

Why would this be?

Some utterly pointless code, strictly for fiddling purposes:
Import pub.threads
Strict

Graphics 800,600
Local t:tthread = New tthread
t.start(loadsnd,"test.ogg")

Local music:TSound
Local channel:TChannel
While Not KeyDown(key_escape)
	Cls
	DrawText "Time: " + CurrentTime(),10,10
	DrawText MilliSecs(),10,30
	If t.status() = t.STOPPED
		If Not channel
			music = TSound(t.result())
			channel = PlaySound(music)
		EndIf
	EndIf
	Flip
Wend

Function loadSnd:Object(data:Object)
	Local s:TSound = LoadSound(String(data))	
	Return s
End Function



GfK(Posted 2008) [#22]
Well... I've stopped it doing the logfile thing by commenting out the line that does it (line 535) in c:\BlitzmaxSVN\mod\brl.mod\blitz.mod\bdwgc\finalise.c
WARN("Finalization cycle involving %lx\n", real_ptr);

That's removing the warning though, not the cause. So, not ideal.