Notifications
Clear all

[Closed] Multithread in Maxscript

Not once I seen peoples argue that we have multithreads in Maxscript.
Of course we can run .net threads (that execute scripts) in Max but
how does this make Maxscript truly multithread?

I’ll be happy if someone prove this somehow with sample code.
Thanks

28 Replies
 lo1

well, what is your definition of ‘truly multithreaded’? What kind of proof do you expect to see?

Here some sample code I did while doing a mini challenge a while back:

http://forums.cgsociety.org/showpost.php?p=6986723&postcount=79

And remember, multi thread != multicore. How threads are distributed over the cores is something the OS takes care of.

Thanks to both.

Rotem, good question multithreads are not very clean subject to me, so it is possible some lack of understanding from my side, maybe Jonathan’s example answer already, s’d check, but here is my definition.

Lets say i have 2 blocks of codes taskA() and taskB(), each one do different job and each one if runs alone will finish it work for 1 second. But if those tasks run at 2 threads they s’d finish for 1 or closer to 1 sec, and for sure not for 2 seconds, right?

Also lets say taskA update some data, global variables for instance, and that happens at time 0.1 sec after it start, when taskB s’d can get fresh data at that time or very soon after that, i.e. not just at the end of taskA (after 1 sec.).

In other words, i suspect that the code blocks executed sequential, and that is not ‘truly multithreaded’, but of’cuz i might be wrong, and this is what i want to clarify to myself.

Thanks in advance!

 lo1

The code blocks are not executed sequentially, they are indeed executed in parallel.
The degree to which code is executed faster as more threads are used is known as the code’s scalability, and is very dependent on the code being executed.
If a block of code executed in X seconds on one thread and in X/N seconds on N threads, then it is said that the code scales linearly, which is the best possible situation.
The ideal number of threads to use for a particular task depends heavily on the code and on the hardware. Most likely using 100 threads for a certain task will be slower than using 10 threads due to the overhead of starting and managing that many threads.

As for code blocks accessing the same data together, it is up to you to ensure your code is ‘thread-safe’, and does not cause fatal collisions. There was a thread on this topic not long ago.
Maxscript does not currently contain any instruments to help you with this task, as it
was not designed for multithreaded execution, but as far as I remember, the main guideline was that you must not redraw the viewport from a background thread.

1 Reply
(@panayot)
Joined: 10 months ago

Posts: 0

Excellent! That was all what I need to know, many thanks!

FYI at time <n> does not work in multiple threads…

but as far as I remember, the main guideline was that you must not redraw the viewport from a background thread.

^ That helps make a background worker thread more stable in some cases, but it can still crash max if the user is doing something when the thread tries to update the viewport when it’s finished (like merging a max file in the background, or creating a box, updating a mesh, etc…). For doing calculations and non-viewport related stuff, the background worker is great. I wish it worked 100% of the time.

I did some race condition tests on the background worker and found that I could not create a fatal collision between two functions operating on the same mesh at the same time. This has led me to believe that a .net background thread does not execute at the same time as the maxscript “main” thread in 3ds max. In addition to that assumption, I think that the main thread has precedence over any background threads, meaning that identical functions will take different times to compute depending on if they are in the main thread or a background thread.

For example, here are two identical functions operating on the same array, yet the background function finishes after the foreground function, despite being called first:


 Race 2:
    (	--which thread finishes last?
   	local myBox = box() --create a box()
   	--create an array, fill the array with references to the box
   	local myArray = #(myBox, myBox, myBox, myBox, myBox, myBox, myBox, myBox, myBox, myBox) 
   
   	fn bkgWrkr =
   	(	--move the box position in a background thread
   		for i=1 to myArray.count do
   			(myArray[i].pos = [0,100,0])
   	)
   	fn foregroundWrkr =
   	(	--move the box position in a foreground thread
   		for i=1 to myArray.count do
   			(myArray[i].pos = [0,0,0])
   	)
   	
   	MainThread = dotnetobject "CSharpUtilities.SynchronizingBackgroundWorker"
   	dotNet.addEventHandler MainThread "DoWork" bkgWrkr
   	MainThread.RunWorkerAsync()
   	foregroundWrkr()
   )	--in this example, the bkgWrkr always finishes last (for me) 
 

I think any function put into a background thread takes a slight performance hit due to the precedence of the main thread, if any code is executing in the main thread. So, if you care about performance and stability, you probably shouldn’t put your code in a background thread. And I love the background thread too – I’m not hatin’ – just being honest about my discoveries.

 lo1

I have a few remarks about this race test:

  • It is somewhat unfair due to the extremely fast task you are measuring. If you benchmark this task it takes less than 1 milisecond for the foreground worker to complete, so by the time the background worker sets up a thread to work with, the foreground worker is done.

  • Consider this test instead:

(	--which thread finishes last?
   	local myBox = box() --create a box()
   	--create an array, fill the array with references to the box
   	local myArray = for i = 1 to 200 collect myBox
	global bgStart = undefined, fgStart = undefined, bgEnd = undefined, fgEnd = undefined
   
   	fn bkgWrkr =
   	(	
		::bgStart = timestamp()
   		for i=1 to myArray.count do
   			(myArray[i].pos = [0,100,0])
		::bgEnd = timestamp()
   	)
   	fn foregroundWrkr =
   	(
		::fgStart = timestamp()
   		for i=1 to myArray.count do
   			(myArray[i].pos = [0,0,0])
		::fgEnd = timestamp()
   	)
   	
   	MainThread = dotnetobject "CSharpUtilities.SynchronizingBackgroundWorker"
   	dotNet.addEventHandler MainThread "DoWork" bkgWrkr
   	MainThread.RunWorkerAsync()
   	foregroundWrkr()
	sleep 2 --let everyone catch up
	format "BG thread started % ms after FG thread and finished % ms after it
" (bgStart-fgStart) (bgEnd-fgEnd)
   )

half the times you will get a result proving that the BG worker started working after the FG worker, and finished its work by the same offset.
Other times, max will crash due to the race condition.

 lo1

I think that is a somewhat misleading statement.
If your task can benefit from parallel execution, you should probably put it in a background thread, ideally more than one. That is the whole idea of multithreading.
If your task does not benefit from parallel execution, or due to thread conflicts is not stable when executed in parallel, you shouldn’t put your code in a background thread.

I think that is a somewhat misleading statement.

I agree with you now, looking back on that statement.

Page 1 / 3