[How-To] Run Scripts on Crack (Multi-threading)

Threading in FiveM

-- random block of stuff so there's a space between the title and the thread!

I haven’t seen a proper guide on multi-threading, and there’s a lot of myths and misconceptions about what it actually is; read this and you’ll be a smarter scum of the earth.


I am writing this tutorial assuming you have surpassed ‘basic beta male server owner

This means, you have done the following;

  • Properly set up your FXServer
  • Have at least once, successfully operated a text editor and opened a lua file
  • Stopped begging for help with ESX errors in the #fxserver-support channel.

This being said, I am going to use a few terms which you may not yet be familiar with, so here they are;

  • Thread : a split process operation that can run simultaneously with the main program
  • Synchronous : code that runs in order of writing
  • Asynchronous : code that runs instantaneously, with no delays for previous tasks
  • Multi-threading : running multiple threads of execution concurrently

And a few terms I won’t use for fear of hurting brains, but you should research if you ever care to advance further towards becoming an ‘assembly god chad’ and less of a 'python script kevin’

  • TLB’s : Translation Lookaside Buffers (the reason multithreading, or single thread operations can go to shit)
  • Multiprocess Thread Joining : the process of joining threads to new processes so as not to interupt the creation of new threads within a process
  • Thread Limitations : the physical hardware and software limitations that require thread termination (bad, don’t do that) or thread joining (good, do that) with the main process/subprocesses

Finally; before we get into things, a few myths about threading debunked;

  • Threads are Asynchronous : not true, a thread can run parallel with it’s parent script but the contents of a thread run synchronously.
  • Threads make fxserver go boom : again, not true - use them intelligently and you’ll find them quite useful.
  • wHy NoT PuT EvErY liNe In A ThReAd : http://sites.rootsweb.com/~nyononda/hospitals/idiotsasylum.html

What is threading in FiveM?

Threads in FiveM allow us to run operations within code instantaneously, without affecting the primary operation of the code. This is useful if you have an operation that needs to run that could take a while to complete, but you do not want to interrupt the rest of the script. For instance; iterating through a LARGE amount of data. It can also be used within a loop in order to very quickly complete an indexing operation, but more on that later.

In order to create a basic thread;

Citizen.CreateThread(function()
 -- code to be executed on a separate thread
end)

This will spawn a thread separate of the primary process. I will continue to refer to each .lua file as the ‘primary process’


How Threads Work

Threads allow you to execute code that would otherwise halt the entire operation at the same time that the parent code in the main process continues to execute. In more advanced configurations, it allows you to complete slow and laborious tasks extremely quickly.

Examine the following code;

print("First Print")
Wait(1000)
print("Second Print")

In this example, the console will output “First Print” and then one second later it will output “Second Print”

However, using threading;

-- First Thread
Citizen.CreateThread(function()
     print("First Print")
     Wait(1000)
     print("Second Print")
end)

-- Second Thread
Citizen.CreateThread(function()
     Print("Third Print")
end)

In this example, the console output will look like this

“First Print”
“Third Print”
(one second delay)
“Second Print”

So how’d that happen? Because we’re using threads, the code executed without acknowledging the Wait within the first thread, and continued to spawn the second thread instantaneously. The Wait was only acknowledged within the context of the first thread, and the operations within.


Hypothetical Usage

Okay, so let’s make this useful.

I have the following code; (note - this is hypothetical, sloppy, and I don’t see why you would do this but, it’s a good example )

-- assume a table with a true/false entry for each player on the server
-- when a player types /confirm their entry is changed to true

for i=1,32 do
    if table[i] then
       while table[i].status == false
             Wait(0)
       end
      
       print("Player has used /confirm")
    end
end

The problem with this code, is that it will hang up on the first valid table until /confirm is typed by that player; we can solve this problem by threading;

-- assume a table with a true/false entry for each player on the server
-- when a player types /confirm their entry is changed to true

for i=1,32 do
    if table[i] then
          Citizen.CreateThread(function()
                 while table[i].status == false
                     Wait(0)
                 end
      
                      print("Player has used /confirm")
          end)
    end
end

This will spawn a thread for every VALID entry, and allow the loop to run all the way to 32.


Loops In Threads

Just because your thread is separate from the main process, does not mean it cannot cause issues for the parent. For instance, creating a while loop with no wait will still crash your clients or server.

-- this is an example of improper loop usage within a thread

Citizen.CreateThread(function()
   while true do
      print("y does essentialmode databus not work 4 me ?? ? ? ? !!!")
   end
end)

Also; never, ever, ever, ever, ever, ever, ever, do this -

while true do
    Citizen.CreateThread(function()
         print("hello im here to overload your thread limitz")
    end)
end

Spawning threads within a while loop with no foreseeable end is a terrible idea. I am unsure of the ‘thread limit’ within FiveM, however I do know that creating a million threads will cause a bunch of bad stuff to happen.


Understanding Thread-based Problems

The use of threads in any language can generate problems, and unexpected results should you not understand them fully. I’m going to go over what I believe to be the most common issues that come from the use of threads;

The problem with multithreading, is that it does not alleviate the resource usage that would come from doing each operation without a thread. For instance, in python, let’s use the following hypothetical.

In python, my code has pulled json information from a web-server and parsed that information into table structure.

I now want to iterate through that table structure for every field named ‘itemname’ and there are over 10,000 items, and I then want to write that item name to a file.

Without threading, it would look like this ( not python syntax for understanding purposes )

GET <url>

<PARSE JSON TO VARIABLE 'ITEMS'>

<LOOP THROUGH EACH ITEM AND WRITE ITEMNAME TO ITEMNAMES.TXT>

There would be an operation delay within the loop, as it would wait for the write operation to finish before it continues on to iteration N + 1. However, if we were to insert a thread before the write portion of that loop, we would be attempting to write 10,000 items to a file all at once, as well as allowing that loop to run away until it reached item 10,000. This would require a significant amount of processor usage and disk usage in order to complete, not to mention hitting the thread limit for python ( which is hardware based, in my case roughly 950 threads ).

This is why when creating a thread in FiveM, especially within a loop, you have to acknowledge the potential resource usage that will come from the operation you are performing. Implement proper process sleep timers between the creation of threads within a loop, and acknowledge the total thread count that will come from your application.

Another thread based issue results from a lack of understanding of the bridging between synchronous execution and asynchronous execution when implementing threads inside of a synchronous block of code - as displayed in our print example.

Citizen.CreateThread(function()
     Wait(3500)
     print("one")
end)

Wait(2500)

Citizen.CreateThread(function()
    Wait(100)
    print("two")
end)

print("three")

In the above example, the output will look like this;
“three”
“two”
“one”

This is because the threads run separately from the main process. In our first thread, we wait 3500ms, which is longer than the 2500ms wait in the main process. The second thread waits 100ms, but the final print of “three” will execute at the same time as the new thread being spawned, which is told to wait 100ms before printing “two.”


This was brain to keyboard; hope you find it useful.

stuff and things

tl;dr threadz are cool but can make your pc go kaboom

i didn’t proofread any of this so half of it probably just sounds like ramble

yes i’m aware citizen.createthread() is actually just a function registering a coroutine and calling it, go away.

i swear to god if i see one more resource with an entire .lua file encased in Citizen.CreateThread i’m putting your dog on wikileaks

32 Likes

Great!, now how yo stop a thread (not using terminateThread)

1 Like

stop a thread in lua with return

2 Likes

I am using C# and I have a few questions:

  • Is it possible to call LoadModel() and similar stuff on a secondary thread, to avoid blocking the game?
  • Should I create threads normally like I would in a normal C# program, or do I need to call a specific API method?

wait, so where the heck is the crack?

This is Lua tutorial and threads works like js here, there are some event loop and 1 thread physically.
In C# you probably need to implement your own threading logic.

You get three, two, one even in this case:

Citizen.CreateThread(function()
     Wait(200)
     print("one")
end)

Wait(2500)

Citizen.CreateThread(function()
    Wait(100)
    print("two")
end)

print("three")

Wait() outside CreateThread function have no sence. Wait is sugar around coroutine.yield. coroutine.yield works only inside coroutine.

And… there is no mutithreading in scripts at all. all coroutines executes one by one in one thread.
Just look at scheduler code:

calling coresume(coroutine.resume) for each “thread”. Its synchronous function which return result when you call Wait(coroutine.yield)

print("hello im here to overload your thread limitz")

There are no thread limits.
CreateThread simple start new coroutine and add it to map:

But each thread consume CPU when you call Wait. Wait is yield. yield is expensive. Each cal of wait is saving and restoring coroutine context.

I have to agree with this. Myself and a friend both run servers with a lot of separate resources and it seems that every single script is run on a single core. Which is unfortunate seeing as how if that weren’t the case most people wouldn’t have performance issues on script heavy RP servers. I have my CPU overclocked to 4.9GHz and still get poor framerate at times on some servers.

I’m curious now, though; Do C# scripts do the same thing? I feel like they do from what I’ve seen.

It can’t ‘not be the case’. Even if trying to run stuff ‘on multiple cores’ it’d have so much lock contention and sequencing/scheduling overhead that it’d be slower to do so: game loops are inherently linear so aren’t parallelizable at all.

Also, ‘script heavy’ means rather ‘needing user scripts to be made faster’, not ‘hurr blame FiveM’s design even though it’s Rockstar’s design’. It’s often fairly doable to get a lower total tick time, how else do you think GTAO itself manages to run fairly fast for example despite running a lot of ambient scripts?

The performance issues on “script heavy RP servers” is more on the people making the resources. Server-side hitches tend to be caused by throwing out hundreds (sometimes thousands) of function references “at once”. Of course, if you intend on running a server you should look to optimising the resources you use as much as possible.

I’ve pointed this resource out to a few people, as it’s rather popular.

It (pointlessly) calls server events to retrieve a player object from ESX and send them a client event, with optional logging. The events being called would also trigger a server event for updating the player object. For a single player this could happen several times a second - once you have a decent player count it basically starts to overload the scheduler.

Now looking at the client, you’ve got 11 threads being created and checked every tick - once they meet the requirements they will yield for a little while but it’s not like they aren’t still having an impact, as stated above.

All of esx_status, esx_basicneeds, and utk_stress can run in a single resource and thread, to bring total usage down to 0.02ms each time it ticks (instead of each one running at ~0.02ms to ~0.04ms).

Basically, servers that run like garbage and have a 4ms CPU time at all times are incredibly popular despite having nobody capable of more than editing a config.
2 Likes

oh, that could be a fun example resource to see if some of the serialization overhead can be reduced. still, i agree, what is with that resource’s design? why are the comments in a weird language and is the indentation entirely random? why is there a resource inside a resource? why is it using events where it should use exports? where did this Wait(1) usage come from as opposed to the more logical 0? why is it using a server event just to return the same value to the client again???

and then people keep going ‘fivem is unoptimized and everyone knows it, when will you fix it’ to us

5 Likes

I actually use Wait(5) but I haven’t tested for performance differences with it, I just pushed it back as far as I could before flickering occurs - the woes of learning to code based on FiveM resource releases.

While we’re on the subject and I have your attention, I tried enabling lua 5.4 but noticed performance issues - not sure if it’s just me being bad, scheduler, or something strange in the implementation.

CreateThread(function()
	while true do
		Wait(0)
	end
end)

0.01ms with 5.3, ~0.03ms when using 5.4
The more I put into the thread the bigger the performance gap.

Edit: Could be wrong on the performance increasing from putting more into the thread, but it definitely gets an extra 0.02ms just being active so I need to consider the benefits to memory and some of the functions (as I understand table.clone and string.strjoin are more efficient than creating my own functions to perform the action), and the garbage collection works a lot better. I suppose I am being a little too nitpicky on the small cpu cost.

Honestly though, everything runs great and the issues most people run into are due to poor practices and not grasping basic concepts or and unwillingness to look into cause and effect. I’m having a lot of fun testing the difference in performance and memory use when using different methods, but people just want to drop a function in and call it a day.

1 Like

That makes no sense, though. Wait will round to the nearest frame (as it’s tick-based) so all you’re doing here is causing problems for anyone with over 200 FPS at no noticeable other gain.

If you want to run something every frame, use 0, don’t try to be clever with values between 1 and 15 or so.

2 Likes

Makes sense and yeah I considered the possibility of it just being a placebo effect. I’ve got plenty of little habits I need to reconsider.

If you think that’s bad, you should see what happens when 1000 of those player function references occur at once (and yes, there are many resources doing this).

1 Like

I’m not saying that a single loop should be run on multiple cores. But the way it is now it seems that every single resource is run on the same core, along with a lot of vanilla game logic being run on that core as well. I would have expected it to be possible to run scripts on a separate thread to the game engine, but I have little experience with coding other than writing scripts for games like FiveM and Arma

I wasn’t trying to point fingers or really blame anything. I was simply saying it’s unfortunate that the scripts aren’t multithreaded, as I wasn’t aware there was a game engine limitation inhibiting it from being the case. Or were you directing that more at how a portion of the community in general blames FiveM for it?

I don’t disagree with this, I’ve taken a few of the scripts I’ve downloaded and cut the CPU time down by more than half. It seems a lot of scripts run everything in a single 0ms or 1ms loop. Usually I leave the rendering with DrawText() in the original 1ms loop and putting everything else in a 200ms loop. Btw, is there a more efficient way of rendering on-screen than DrawText() in LUA?

So is it actually more efficient to run all the stuff in a single Citizen.CreateThread() if it’s all on the same loop timing?

I once saw a resource nearly double in CPU time going from a 1ms Citizen.Wait() to a 0ms one. However last time I tested it I couldn’t reproduce the same phenomenon. I’ve actually been somewhat concerned about the 1ms loops, as I like to future proof things and if someone is somehow still using one of these scripts 10 years from now and we’ve got 1KHz+ gaming monitors then any rendered text will flicker.

1 Like

Since threads are only able to run one-by-one and, as stated

But each thread consume CPU when you call Wait. Wait is yield. yield is expensive. Each cal of wait is saving and restoring coroutine context.

This isn’t everything that I did and only merged some of the utk_stress triggers with basicneeds. Each trigger has a variable to count down so they can’t run on every tick, and the tick is called by the proper event instead of creating a new thread. Checking variables is cheap.

It also adds up the total stress in order to trigger a single event for adding or removing stress from the player.

You’re better off checking if a variable is “off its timer” than having a bunch of different coroutines call wait so you can get away with having a lot of code in a single thread, though doing it on frame might get a bit silly. I’ll typically setup two threads to handle all my needs and adjust the wait time if it’s appropriate. Being able to easily create and clear a thread is useful, too.

If it’s a C# resource just use the built in await / async as with any C# async programming.

A trick that can be create only once loop in some code quote

function OpenLoop(Break)
	Wait(500)
	local a = math.random(0,100)
	if a == 50 then 
		print('break the loop')
		Break()
	end 
end 





--Create Once Loop Which Can Be Breakable with remote function 
local ThisEnd
if ThisEnd == nil then 
	ThisEnd = false 
	CreateThread(function()
		while not ThisEnd do Wait(33)
			local Break = function()
				ThisEnd = true 
			end 
			OpenLoop(Break)
		end 
		return
	end)
end 
1 Like

A simple control while loop snippet relative of this topic

function Loop (duration,fn)
    local duration = duration 
    local loopend = false 
    local fn = fn 
    local newthread = function() end 
    local localcb = function(action,value) 
        if action == "break" or action == "kill" then 
            loopend = true
        elseif action == "set" then 
            duration = value
        elseif action == "get" then 
            return duration
        elseif action == "restart" then 
            loopend = false
            return newthread()
        end 
    end
    newthread = function() 
        CreateThread(function()
            while not loopend do  Wait(duration)
                fn(localcb)  
            end 
            return 
        end) 
        return localcb
    end 
    return newthread()
end 

local myloop = Loop(1000,function(this)
    print(this("get"))
    this("set",3000)
end)

CreateThread(function()
    Wait(8000)
    myloop("break")
end)

Advanced

--https://github.com/negbook/tasksync/blob/main/snippets_without_tasksync/simpleloop.lua
loop_newthread = function (duration,fn,fnclose)
    local duration = duration 
    local loopend = false 
    local fn = fn 
    local fnclose = fnclose
    local fns = nil
    local newthread = function() end 
    local localcb = function(action,value) 
        if action == "break" or action == "kill" then 
            loopend = true
        elseif action == "inserttask" then 
            local insertfn = value
            if fns == nil then 
                fns = {fn}
            end 
            table.insert(fns,insertfn)
        elseif action == "removetask" then 
            local idx = value
            table.remove(fns,idx)
        elseif action == "set" then 
            duration = value
        elseif action == "get" then 
            return duration
        elseif action == "restart" then 
            loopend = false
            return newthread()
        end 
    end
    newthread = function() 
        CreateThread(function()
            while not loopend do  Wait(duration)
                if fns then 
                    for i=1,#fns do 
                        fns[i](localcb,i)
                    end 
                else 
                    fn(localcb)
                end 
            end 
            if fnclose then fnclose() end
            return 
        end) 
        return localcb
    end 
    return newthread()
end 

local running = {} 
looponce_newthread = function (name,duration,fn,fnclose)
    if not running[name] then
        local duration = duration 
        local loopend = false 
        local fn = fn 
        local fnclose = fnclose
        local fns = nil
        local newthread = function() end 

        local localcb = function(action,value) 
            if action == "break" or action == "kill" then 
                loopend = true
            elseif action == "inserttask" then 
                local insertfn = value
                if fns == nil then 
                    fns = {fn}
                end 
                table.insert(fns,insertfn)
            elseif action == "removetask" then 
                local idx = value
                table.remove(fns,idx)
            elseif action == "set" then 
                duration = value
            elseif action == "get" then 
                return duration
            elseif action == "restart" then 
                loopend = false
                return newthread()
            end 
        end
        running[name] = localcb 
            newthread = function() 
                CreateThread(function()
                    local _cb_ = running[name]
                    while not loopend do  Wait(duration)
                        if fns then 
                            for i=1,#fns do 
                                fns[i](_cb_,i)
                            end 
                        else 
                            fn(_cb_) 
                        end 
                    end 
                    if fnclose then fnclose() end
                    running[name] = nil
                    return 
                end) 
                return localcb
            end 
            return newthread()
         
    end 
end 
looponce_newthread_delete = function(name)
   if running[name] then running[name]("break") end 
end 

I’m sorry to rez an old thread for something not directly related to the main topic, but I notice you mention “should use exports”. Is there any information on when/where you should use an export over an event and why? From my understanding of exports they just trigger a backend event in the end themselves.

1 Like