FiveM CreateThread คืออะไร? ใช้อย่างไรไม่ให้ Script กิน CPU

FiveM CreateThread() คือ Function ของ CfxLua สำหรับสร้าง Thread หรือ Coroutine ใหม่ เพื่อให้ Code ชุดหนึ่งสามารถทำงานแยกจาก Flow หลักของ Script ได้ โดยพบได้บ่อยมากในระบบที่ต้องทำงานต่อเนื่อง เช่น ตรวจตำแหน่ง Player, ตรวจปุ่ม, วาด Marker, รอโหลด Model หรือทำงานเป็นช่วงเวลา

ตัวอย่างพื้นฐานคือ

CreateThread(function()
    print('Thread started')
end)

หรือเขียนชื่อเต็ม

Citizen.CreateThread(function()
    print('Thread started')
end)

ทั้งสองรูปแบบพบได้ใน FiveM Lua

แต่จุดสำคัญที่สุดที่ต้องจำคือ หากใช้ while true do ภายใน Thread ต้องมี Wait() ในเส้นทางการทำงานของ Loop ไม่เช่นนั้น Client อาจค้างหรือเกมอาจ Crash ได้

ตัวอย่างที่ถูกต้องคือ

CreateThread(function()
    while true do
        Wait(1000)

        print('Running every second')
    end
end)

บทความนี้จะอธิบาย CreateThread() ตั้งแต่พื้นฐานไปจนถึงการใช้ร่วมกับ Wait(0), Native, Marker, Controls, Adaptive Sleep และการ Optimize Resource

① FiveM CreateThread คืออะไร

CreateThread() ใช้สร้าง Thread ใหม่ใน CfxLua Runtime

Syntax หลักคือ

CreateThread(function()
    -- code
end)

ชื่อเต็มคือ

Citizen.CreateThread(function()
    -- code
end)

เอกสาร Cfx.re ระบุว่า Code ภายใน Handler ของ CreateThread() จะถูก Execute แบบ Asynchronous จาก Flow ที่สร้าง Thread นั้น

ตัวอย่าง

print('A')

CreateThread(function()
    print('B')
end)

print('C')

แนวคิดคือ Script ไม่จำเป็นต้องหยุดรอให้ Thread จบก่อนจึงทำ Code หลัง CreateThread() ต่อ

② CreateThread ใช้ทำอะไร

Use Case ที่พบได้บ่อย เช่น

  • ตรวจ Player Position

  • ตรวจ Player State

  • ตรวจ Keyboard/Controls

  • Draw Marker

  • Draw Text

  • ตรวจ Vehicle

  • รอ Model โหลด

  • รอ Animation Dictionary

  • Periodic Cleanup

  • Update Cache

  • Background Logic

  • Polling ที่จำเป็น

  • Gameplay Loop

ตัวอย่างระบบ Shop

Thread
↓
ตรวจตำแหน่ง Player
↓
ใกล้ Shop หรือไม่
↓
แสดง Marker
↓
ตรวจปุ่ม E
↓
เปิด Shop

นี่เป็น Pattern FiveM ที่พบได้บ่อยมาก

③ CreateThread เป็น OS Thread จริงไหม

ไม่ควรมองว่าเป็น Operating System Thread แยกเต็มรูปแบบเหมือนการสร้าง Thread ระดับ OS

Wait() ของ CfxLua ทำงานโดยอาศัย Lua Coroutines

ดังนั้น Concept ที่เหมาะกับ Developer คือ

CfxLua Scheduler
↓
Coroutine / Thread
↓
Yield ด้วย Wait()
↓
กลับมาทำงานอีกครั้งภายหลัง

การเข้าใจเรื่อง Yield สำคัญกว่าคิดว่าเป็น CPU Thread แยกคนละ Core

④ CreateThread ต่างจาก Function ปกติอย่างไร

Function ปกติ เช่น

local function test()
    print('Hello')
end

test()

จะถูกเรียกตาม Flow ปัจจุบัน

แต่

CreateThread(function()
    -- asynchronous thread logic
end)

สร้าง Coroutine/Thread ใหม่ให้ Scheduler จัดการ

จึงเหมาะกับ Logic ที่ต้อง

รอ
วน
ทำงานเป็นระยะ

โดยไม่ต้อง Block Main Flow ของ Resource

⑤ ตัวอย่าง CreateThread แบบง่าย

CreateThread(function()
    print('Hello from thread')
end)

เมื่อ Resource ทำงาน Thread จะถูกสร้างและ Handler จะถูก Execute

ถ้าต้องการ Delay

CreateThread(function()
    Wait(5000)

    print('5 seconds later')
end)

Code จะ Yield ก่อนประมาณ 5 วินาทีแล้วจึงทำงานต่อ

⑥ Citizen.CreateThread กับ CreateThread ต่างกันไหม

สำหรับ FiveM Lua คุณจะพบทั้ง

Citizen.CreateThread(function()
end)

และ

CreateThread(function()
end)

รูปแบบสั้นถูกใช้แพร่หลายใน Script ปัจจุบัน

เช่นเดียวกับ

Citizen.Wait(1000)

ที่สามารถเขียนเป็น

Wait(1000)

ได้

ดังนั้นถ้าเห็น Script เก่าและใหม่ใช้ Syntax ต่างกัน ไม่จำเป็นต้องคิดว่าเป็นระบบคนละชนิด

⑦ CreateThread ใช้กับ while true ได้อย่างไร

Pattern ที่พบมากที่สุดคือ

CreateThread(function()
    while true do
        Wait(1000)

        print('Loop')
    end
end)

Flow คือ

CreateThread
↓
while true
↓
ทำงาน
↓
Wait
↓
Yield
↓
Scheduler
↓
กลับมาทำงานต่อ

Loop สามารถทำงานต่อเนื่องได้โดยไม่พยายามยึด Execution ตลอดเวลา

⑧ ทำไม while true ต้องมี Wait

เพราะ Loop แบบนี้

CreateThread(function()
    while true do
        print('Loop')
    end
end)

ไม่มีจุด Yield

Thread จะพยายามวนต่อเนื่องโดยไม่คืนการควบคุมให้ Scheduler

Cfx.re เตือนชัดเจนว่า while true do ที่ไม่มี Wait() สามารถทำให้ Client Game ค้างหรือ Crash ได้

ดังนั้นกฎพื้นฐานคือ

ถ้าเป็น Infinite Loop ใน CfxLua ต้องแน่ใจว่ามีเส้นทางที่เรียก Wait() อย่างแน่นอน

⑨ ตัวอย่างที่อันตราย

CreateThread(function()
    while true do
        local ped = PlayerPedId()
    end
end)

Code นี้ไม่มี

Wait()

จึงไม่ควรใช้

แก้ขั้นต่ำเป็น

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

        local ped = PlayerPedId()
    end
end)

แต่ยังต้องถามต่อว่า

จำเป็นต้องหา Player Ped ทุก Game Tick จริงหรือไม่?

Wait(0) ไม่ได้แปลว่า Optimize แล้ว

⑩ Wait คืออะไร

Wait() ใช้ Pause Execution ของ Coroutine ปัจจุบันตามจำนวน Milliseconds ที่กำหนด

ตัวอย่าง

Wait(1000)

หมายถึงให้ Thread Yield แล้วกลับมาทำงานต่อภายหลังประมาณ 1,000 ms

ตัวอย่าง

CreateThread(function()
    print('Start')

    Wait(2000)

    print('2 seconds later')
end)

Wait() ทำงานภายใน Coroutine เช่น Thread ที่สร้างด้วย CreateThread()

⑪ Wait(0) คืออะไร

Cfx.re ระบุว่า

Wait(0)

จะ Pause Coroutine ปัจจุบันจนถึง Game Tick ถัดไป

ดังนั้น Pattern

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

        -- code
    end
end)

หมายถึง Logic ถูกประเมินประมาณทุก Game Tick

เหมาะกับงานที่จำเป็นต้องทำระดับ Frame/Tick จริง ๆ

⑫ Wait(1000) คืออะไร

Wait(1000)

หมายถึงพัก Thread ประมาณ 1 วินาที

ตัวอย่าง

CreateThread(function()
    while true do
        Wait(1000)

        print('Every second')
    end
end)

เหมาะกับ Logic ที่ไม่ต้อง Update ทุก Frame

เช่น

  • Refresh Cache

  • ตรวจ State บางชนิด

  • Cleanup

  • Periodic Check

⑬ Wait เป็นเวลาที่แม่น 100% ไหม

ไม่ควรมองว่า Wait(1000) เป็น Real-time Timer ที่มีความแม่นระดับ Hard Real-time

Cfx.re ใช้คำว่าพักประมาณตามจำนวน Milliseconds ที่กำหนด และ Scheduling ยังขึ้นกับ Runtime/Game Tick

ดังนั้นใช้ได้ดีสำหรับ Gameplay และ Scheduling ทั่วไป แต่ไม่ควรสร้างระบบที่ต้องการความแม่นระดับ Microsecond ด้วย Wait()

⑭ Game Tick คืออะไร

Game Tick คือรอบที่ระบบเกมและ Script Scheduler ทำงาน

สำหรับ Client เวลาแต่ละ Tickสัมพันธ์กับ Frame Rate ได้

Cfx.re ยกตัวอย่างว่า

60 FPS
≈ 16.6 ms ต่อ Tick

180 FPS
≈ 5.5 ms ต่อ Tick

ดังนั้นจำนวน Tick ต่อวินาทีไม่จำเป็นต้องเท่ากันทุกเครื่อง

นี่เป็นเหตุผลสำคัญว่าทำไม Wait(0) หมายถึง “Tick ถัดไป” ไม่ใช่จำนวน Milliseconds คงที่

⑮ ทำไม Wait(5) ไม่เท่ากับทุก Frame

ถ้าระบบต้องทำงานทุก Frame ควรใช้

Wait(0)

Cfx.re เตือนว่าการใช้

Wait(5)
Wait(10)

อาจพลาด Tick บนเครื่องที่ Frame Rate สูง

ดังนั้นถ้ากำลัง Draw บางสิ่งที่ต้อง Render ทุก Frame ควรเลือก Wait(0)

⑯ อะไรจำเป็นต้องใช้ Wait(0)

ตัวอย่างงานที่อาจต้องทำทุก Frame เช่น

  • DrawMarker

  • DrawText บางระบบ

  • ตรวจ Control ที่ต้องการ Response ต่อเนื่อง

  • DisableControlAction

  • Rendering บางประเภท

  • Scaleform Draw

  • Gameplay State บางชนิด

ตัวอย่าง

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

        DrawMarker(
            1,
            100.0,
            100.0,
            30.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
            1.0,
            1.0,
            255,
            255,
            255,
            150,
            false,
            false,
            2,
            false,
            nil,
            nil,
            false
        )
    end
end)

Marker ที่ต้องแสดงต่อเนื่องมักต้อง Draw ซ้ำ

⑰ ทุก Thread ควร Wait(0) ไหม

ไม่

นี่เป็นข้อผิดพลาดที่พบบ่อย

สมมติแค่ต้อง Update Player Job Cache ทุก 5 วินาที

ไม่ควรทำ

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

        -- update cache
    end
end)

ควรเป็น

CreateThread(function()
    while true do
        Wait(5000)

        -- update cache
    end
end)

หรือดีกว่านั้น ใช้ Event Update เมื่อ Job เปลี่ยน หากระบบมี Event รองรับ

⑱ Event ดีกว่า Polling เมื่อไร

สมมติ Player Job เปลี่ยนเฉลี่ยไม่กี่ครั้งต่อ Session

ไม่จำเป็นต้องทำ

ทุก Tick
↓
เช็ก Job

ถ้า Framework มี Event

Job Changed
↓
Update Cache

Event-driven Approach จะลดงานที่ไม่จำเป็น

หลักคือ

ข้อมูลเปลี่ยนเฉพาะตอนมีเหตุการณ์
→ ใช้ Event

ข้อมูลต้องตรวจต่อเนื่องจริง
→ ใช้ Thread/Loop

⑲ Polling คืออะไร

Polling คือการตรวจ State ซ้ำเป็นช่วง ๆ

ตัวอย่าง

CreateThread(function()
    while true do
        Wait(1000)

        local ped = PlayerPedId()

        -- check something
    end
end)

ทุกวินาที Script ถามว่า State เป็นอย่างไร

Polling ไม่ได้ผิด แต่ควรใช้ Interval ให้เหมาะสม

⑳ CreateThread กับ Player Coordinates

ตัวอย่างง่าย

CreateThread(function()
    while true do
        Wait(1000)

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(ped)

        print(coords)
    end
end)

Code นี้อ่านตำแหน่ง Player ทุก 1 วินาที

ถ้าเพียงต้อง Debug Coordinates อาจเพียงพอ

ไม่จำเป็นต้องใช้ Wait(0)

㉑ CreateThread กับ Marker

Marker เป็น Use Case ที่ดีสำหรับ Adaptive Thread

แทนที่จะ Draw/คำนวณหนักตลอดแม้ Player อยู่ไกล สามารถทำ

อยู่ไกล
→ Sleep นาน

เข้าใกล้
→ Sleep สั้น

ต้อง Draw
→ Wait(0)

ช่วยลด Client CPU

㉒ Adaptive Wait คืออะไร

ตัวอย่าง

local shopCoords =
    vector3(
        25.7,
        -1345.2,
        29.5
    )

CreateThread(function()
    while true do
        local sleep = 1000

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(ped)

        local distance =
            #(coords - shopCoords)

        if distance < 20.0 then
            sleep = 0

            DrawMarker(
                1,
                shopCoords.x,
                shopCoords.y,
                shopCoords.z - 1.0,
                0.0,
                0.0,
                0.0,
                0.0,
                0.0,
                0.0,
                1.0,
                1.0,
                1.0,
                255,
                255,
                255,
                150,
                false,
                false,
                2,
                false,
                nil,
                nil,
                false
            )
        end

        Wait(sleep)
    end
end)

เมื่อ Player อยู่ไกล Thread Sleep 1 วินาที

เมื่ออยู่ใกล้ Marker จึงทำงานทุก Tick

㉓ Adaptive Wait ดีกว่า Wait(0) ตลอดอย่างไร

สมมติ Server มี Shop 100 จุด

ถ้า Script ทำ

ทุก Frame
↓
ตรวจ Shops ทั้งหมด
↓
คำนวณ Distance
↓
Native Calls

ตลอดเวลา Client อาจทำงานเกินจำเป็น

Adaptive Wait ช่วยลด Work เมื่อ Player ไม่ได้อยู่ใกล้ Feature นั้น

แต่ต้อง Optimize Algorithm ด้วย ไม่ใช่แค่เพิ่ม Sleep

㉔ ระวัง Bug ของ Adaptive Sleep

สมมติเขียน

local sleep = 0

CreateThread(function()
    while true do
        if condition then
            sleep = 1000
        end

        Wait(sleep)
    end
end)

แล้วไม่ได้ Reset sleep

Thread อาจคง Sleep 1000 ms แม้ Condition เปลี่ยนแล้ว

Pattern ที่ปลอดภัยกว่าคือกำหนดใหม่ทุก Loop

while true do
    local sleep = 1000

    -- logic

    Wait(sleep)
end

จากนั้นลดค่าตามสถานการณ์

㉕ CreateThread กับ Controls

ตัวอย่างตรวจปุ่ม E

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

        if IsControlJustReleased(
            0,
            38
        ) then
            print('E released')
        end
    end
end)

เพราะ Input ที่ต้องการ Response ต่อ Frame มักใช้ Wait(0)

แต่ควรเปิด Logic นี้เฉพาะเมื่อจำเป็น

เช่นตรวจ E เฉพาะเมื่อ Player อยู่ใกล้ Interaction Point

㉖ ตัวอย่างตรวจปุ่มแบบ Optimize กว่า

local nearShop = false

CreateThread(function()
    while true do
        local sleep = 1000

        if nearShop then
            sleep = 0

            if IsControlJustReleased(
                0,
                38
            ) then
                TriggerEvent(
                    'shop:open'
                )
            end
        end

        Wait(sleep)
    end
end)

ถ้า Player ไม่ได้อยู่ใกล้ Shop ก็ไม่ตรวจ Control ทุก Frame

㉗ CreateThread กับ Native

Native จำนวนมากถูกเรียกภายใน Thread

ตัวอย่าง

CreateThread(function()
    while true do
        Wait(500)

        local ped =
            PlayerPedId()

        local inVehicle =
            IsPedInAnyVehicle(
                ped,
                false
            )

        if inVehicle then
            print('Player in vehicle')
        end
    end
end)

ต้องเลือก Interval ตามความไวที่ Feature ต้องการ

㉘ Native ทุกตัวควรเรียกทุก Tick ไหม

ไม่

Cfx.re แนะนำให้หลีกเลี่ยง Heavy Native Calls แบบ Per-tick หากไม่จำเป็น

บาง Native มี Cost สูงกว่า Native ง่าย ๆ

ดังนั้นควรถาม

Native นี้ต้องเรียกทุก Frameหรือไม่?

ถ้าไม่ ให้ลด Frequency

㉙ Cache Native Result ได้ไหม

ได้เมื่อเหมาะสม

ตัวอย่างแทนการเรียก PlayerPedId() ซ้ำหลายแห่งทุก Frame อาจมี Cache Thread

local playerPed = 0

CreateThread(function()
    while true do
        playerPed =
            PlayerPedId()

        Wait(1000)
    end
end)

จากนั้น Code อื่นใช้

playerPed

แต่ต้องพิจารณาว่า Data ที่ Cache เปลี่ยนบ่อยแค่ไหน

Cfx.re เองยกการ Cache Native ที่เปลี่ยนไม่ถี่เป็นหนึ่งในแนวทางลด Native Calling Overhead

㉚ Cache PlayerPedId ตลอดไปดีไหม

ไม่ควรถือว่า Ped Handle ไม่เปลี่ยนตลอด Session

ระบบ Character/Ped Change อาจทำให้ Ped เปลี่ยนได้

จึงควร Refresh Cache เป็นระยะหรืออัปเดตตาม Event/Lifecycle ที่เหมาะสม

Cache มีประโยชน์ แต่ต้องไม่ทำให้ State เก่า

㉛ CreateThread ใช้รอ Model โหลดได้ไหม

ได้ และเป็น Pattern ที่พบมาก

local model =
    joaat('sultan')

RequestModel(model)

while not HasModelLoaded(model) do
    Wait(0)
end

Code นี้ต้องอยู่ใน Coroutine ที่สามารถ Wait() ได้ เช่น Thread หรือ Event Handler ที่รองรับ Yield ตาม Runtime

จากนั้นจึง Create Vehicle

㉜ ทำไมต้อง Wait ตอนโหลด Model

RequestModel() ไม่ได้หมายความว่า Model พร้อมใช้ทันที

จึงต้องตรวจ

HasModelLoaded(model)

ถ้ายังไม่พร้อม

Wait(...)

แล้วตรวจอีกครั้ง

Pattern คือ

Request
↓
ยังไม่โหลด
↓
Yield
↓
ตรวจใหม่
↓
โหลดแล้ว
↓
ทำงานต่อ

㉝ ควร Wait(0) หรือ Wait(500) ตอนโหลด Model

ขึ้นอยู่กับ Use Case

เอกสารตัวอย่าง Cfx.re มีการใช้ Wait() ภายใน Loop ระหว่างรอ Resource/Model

ถ้าต้องการ Response เร็วอาจใช้ Wait(0)

แต่ในบางกรณี Interval ที่มากขึ้นก็เพียงพอ

ต้องสมดุลระหว่าง Response Time และ Work ที่ทำซ้ำ

㉞ CreateThread ใช้บน Server ได้ไหม

ได้

Cfx.re ระบุ Citizen.CreateThread อยู่ทั้งใน Client-side และ Server-side Lua Functions

ตัวอย่าง Server

CreateThread(function()
    while true do
        Wait(60000)

        print(
            'Server periodic task'
        )
    end
end)

เหมาะกับ Background Task บางประเภท

㉟ Server Thread ใช้ทำอะไร

ตัวอย่าง

  • Periodic Cleanup

  • Refresh Cache

  • Scheduled Save

  • State Management

  • Server Maintenance Logic

  • Periodic Log

  • Checking Expired Sessions

แต่ไม่ควรใช้ Loop แทน Event หรือ Database Scheduler ทุกกรณี

㊱ Server Thread กิน CPU ได้ไหม

ได้

Server Loop ที่ทำงานหนักมีผลกับ Server มากกว่า Player คนเดียว

ตัวอย่างอันตราย

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

        for _, playerId in ipairs(
            GetPlayers()
        ) do
            -- heavy work
        end
    end
end)

ถ้า Player จำนวนมากและ Logic หนัก Server CPU อาจเพิ่มสูงมาก

㊲ อย่าวน Player ทุก Tickถ้าไม่จำเป็น

สมมติมี Player 200 คน

Loop ทุก Tickหมายถึง Logic ถูกทำกับ Player จำนวนมากซ้ำอย่างรวดเร็ว

ถ้าระบบต้องตรวจเพียงทุก 5 วินาที ใช้

Wait(5000)

อาจเหมาะกว่า

หรือใช้ Event เมื่อ Player State เปลี่ยน

㊳ CreateThread กับ Database

ไม่ควรสร้าง Thread ที่ Query Database ถี่โดยไม่มีเหตุผล

ตัวอย่างที่ไม่ดี

CreateThread(function()
    while true do
        Wait(100)

        -- SELECT database
    end
end)

อาจสร้าง Database Load สูง

ถ้าข้อมูลสามารถ Cache ใน Memory ให้ Query เมื่อจำเป็นและ Update Cache ด้วย Event จะดีกว่าในหลายกรณี

㊴ Periodic Save ใช้ Thread ได้ไหม

ได้

ตัวอย่าง Concept

CreateThread(function()
    while true do
        Wait(300000)

        -- save required data
    end
end)

300,000 ms คือประมาณ 5 นาที

แต่ต้องออกแบบร่วมกับ

  • Player Disconnect Save

  • Resource Stop

  • Database Transactions

  • Error Handling

ไม่ควรพึ่ง Periodic Save เพียงอย่างเดียวสำหรับข้อมูลสำคัญ

㊵ CreateThread กับ SetTimeout ต่างกันอย่างไร

ถ้าต้องการทำงาน ครั้งเดียวในอนาคต อาจใช้ SetTimeout เหมาะกว่า Thread + Loop

Concept:

CreateThread + while
→ งานต่อเนื่อง

SetTimeout
→ งานครั้งเดียวหลัง Delay

ตัวอย่างที่ไม่จำเป็นต้องสร้าง Loop

SetTimeout(5000, function()
    print('5 seconds later')
end)

เลือกเครื่องมือให้ตรงกับรูปแบบงาน

㊶ CreateThread กับ Event ต่างกันอย่างไร

Thread เหมาะกับ

งานต่อเนื่อง
งานเป็นช่วงเวลา
Polling
Rendering

Event เหมาะกับ

เกิดบางสิ่งขึ้น
↓
ทำงานทันที

ตัวอย่าง Job เปลี่ยน

ไม่จำเป็นต้อง Thread เช็ก Job ทุกวินาที หาก Framework มี Job Changed Event

ใช้ Event จะเหมาะกว่า

㊷ CreateThread กับ Callback ต่างกันอย่างไร

Thread เป็น Execution Flow

Callback เป็น Function ที่ถูกเรียกเมื่อ Operation หรือ Response พร้อม

ตัวอย่าง Database Async อาจทำ

Thread
↓
เรียก Async Operation
↓
Callback
↓
Result

ทั้งสองสามารถใช้ร่วมกัน แต่ไม่ใช่สิ่งเดียวกัน

㊸ CreateThread กับ Promise/Await

Thread ที่เป็น Coroutine สามารถใช้ Async Pattern ที่ Runtime รองรับ เช่น Citizen.Await

Concept

CreateThread
↓
Async Request
↓
Citizen.Await
↓
Yield
↓
Promise Resolve
↓
ทำงานต่อ

ช่วยเขียน Flow ที่ต้องรอ Result ได้อ่านง่ายขึ้น

แต่ต้องระวังไม่สร้าง Sequential Await จำนวนมากโดยไม่จำเป็น

㊹ CreateThread ซ้อน CreateThread ได้ไหม

ทำได้ทางเทคนิค

ตัวอย่าง

CreateThread(function()

    CreateThread(function()
        print('Child thread')
    end)

end)

แต่ไม่ควรสร้าง Thread ซ้อนจำนวนมากโดยไม่มีเหตุผล

ถ้า Logic สามารถอยู่ Thread เดียวหรือใช้ Function/Event ได้ จะ Maintain ง่ายกว่า

㊺ สร้าง Thread ทุกครั้งที่ Player กดปุ่มดีไหม

ขึ้นอยู่กับว่า Thread จบหรือไม่

ตัวอย่างอันตราย:

ทุกครั้งที่กด E สร้าง

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

        -- permanent loop
    end
end)

กด 10 ครั้ง = อาจได้ 10 Infinite Threads

นี่เป็นสาเหตุที่ Resource บางตัว Resmon สูงขึ้นเรื่อย ๆ

㊻ Thread Leak คืออะไร

ในบริบท FiveM มักหมายถึงการสร้าง Thread ที่ไม่จบหรือไม่ถูกควบคุมจนจำนวน Thread สะสม

ตัวอย่าง

เปิด Menu
→ Thread 1

เปิด Menu อีกครั้ง
→ Thread 2

เปิดอีก
→ Thread 3

แต่ Thread เก่ายังทำงานอยู่

ผลคือ

  • CPU สูงขึ้น

  • Logic ซ้ำ

  • Event ซ้ำ

  • Native Calls ซ้ำ

ควรใช้ State ควบคุม Lifecycle

㊼ วิธีหยุด Loop ใน Thread

สามารถใช้ Condition

local running = true

CreateThread(function()
    while running do
        Wait(1000)

        print('Running')
    end

    print('Thread finished')
end)

เมื่อต้องการหยุด

running = false

Loop จะจบเมื่อถึงรอบตรวจ Condition ถัดไป

㊽ ใช้ break ได้ไหม

ได้

ตัวอย่าง

CreateThread(function()
    while true do
        Wait(1000)

        if shouldStop then
            break
        end
    end

    print('Stopped')
end)

เหมาะกับ Temporary Thread เช่น

  • Mission

  • Progress

  • Temporary UI

  • Temporary Interaction

ไม่จำเป็นต้องทำทุก Thread เป็น Infinite Loop

㊾ Temporary Thread ตัวอย่าง

CreateThread(function()
    local endTime =
        GetGameTimer() + 10000

    while GetGameTimer() < endTime do
        Wait(0)

        -- temporary logic
    end

    print('Finished')
end)

Thread นี้ทำงานประมาณ 10 วินาทีแล้วจบ

เหมาะกว่าการสร้าง Permanent Thread หาก Feature มีอายุสั้น

㊿ CreateThread Error แล้ว Thread อื่นหยุดไหม

Error ใน Thread หนึ่งสามารถทำให้ Flow ของ Thread นั้นหยุดหรือเกิด Script Error แต่ไม่ได้หมายความว่า Runtime ทั้งหมดจะทำงานเหมือน Error ใน Function เดียวทุกกรณี

สิ่งสำคัญคือดู

Client
→ F8 Console

Server
→ Server Console

แล้วหาว่า Thread ไหนเกิด Error

51 วิธี Debug CreateThread

เพิ่ม Output จุดสำคัญ

CreateThread(function()
    print('Thread started')

    while true do
        Wait(1000)

        print('Thread tick')
    end
end)

ถ้าเห็น

Thread started

แต่ไม่มี

Thread tick

ให้ตรวจ Error ก่อนหรือภายใน Loop

52 Thread ทำงานซ้ำผิดปกติตรวจอะไร

ตรวจว่า CreateThread() ถูกเรียกกี่ครั้ง

ตัวอย่าง

RegisterNetEvent(
    'example:start',
    function()

        CreateThread(function()
            while true do
                Wait(1000)

                print('Running')
            end
        end)

    end
)

ถ้า Event example:start ถูกเรียก 20 ครั้ง จะสร้าง 20 Threads

ควรมี Guard เช่น

if running then
    return
end

running = true

53 ตัวอย่าง Guard Thread

local running = false

local function startThread()
    if running then
        return
    end

    running = true

    CreateThread(function()
        while running do
            Wait(1000)

            print('Running')
        end
    end)
end

ช่วยป้องกัน Thread ถูกสร้างซ้ำโดยไม่ตั้งใจ

54 CreateThread กับ Resource Stop

เมื่อ Resource Stop Runtime ของ Resource จะถูกหยุดและ Thread ของ Resource ก็ไม่ควรคาดหวังว่าจะทำงานต่อใน Resource เดิม

แต่ถ้า Thread สร้าง Game Entity, Camera, NUI Focus หรือ State ภายนอกบางอย่าง ควรมี Cleanup ผ่าน Resource Lifecycle ตามความเหมาะสม

เช่น

  • ปิด NUI

  • Destroy Camera

  • ลบ Temporary Entity

  • Reset State

55 CreateThread กับ NUI

บาง Resource สร้าง Thread ตรวจว่า UI เปิดหรือไม่

แต่ไม่ควร Poll NUI Stateทุก Frameถ้า Event สามารถบอกได้

ตัวอย่าง

เปิด NUI
→ set uiOpen = true

ปิด NUI
→ set uiOpen = false

แล้ว Thread เฉพาะ Feature ที่ต้องทำ Per-frame ใช้ State นี้ตัดสิน

56 CreateThread กับ ESX

ESX Script ใช้ CreateThread() ได้ตาม CfxLua ปกติ

แต่ข้อมูล Framework ที่มี Event Update อยู่แล้ว เช่น Job/Player Loaded ไม่ควร Poll ทุก Tickโดยไม่มีเหตุผล

ตัวอย่าง

ESX Event
→ Update Cached Player Data

แล้ว Thread ใช้ Cache นั้น

จะดีกว่าเรียก Framework API หนัก ๆ ทุก Frameในหลายกรณี

57 CreateThread กับ QBCore

หลักเดียวกัน

QBCore มี Events สำหรับ Player Data หรือ Job State หลายส่วน

Resource ที่ดีควรผสม

Framework Events
+
Cached State
+
Thread เฉพาะที่จำเป็น

ไม่ใช่

ทุก Frame
→ ขอ QBCore Player Data ใหม่

ตลอดเวลา

58 CreateThread กับ Qbox

Qbox/ox ecosystem เน้น Event, State และ Library API หลายส่วน

การมี Tool ที่ทันสมัยไม่ได้หมายความว่า CreateThread() เลิกใช้

Thread ยังจำเป็นสำหรับ

  • Rendering

  • Controls

  • Nearby Interaction

  • Gameplay Loops

เพียงแต่ควรใช้เมื่อเป็นเครื่องมือที่เหมาะกับงาน

59 CreateThread Performance Checklist

ก่อนสร้าง Thread ใหม่ ให้ถาม

① ต้องใช้ Thread จริงหรือไม่?
② ใช้ Event แทนได้ไหม?
③ Loop ต้องทำทุก Frameหรือไม่?
④ ถ้าไม่ ต้อง Wait เท่าไร?
⑤ มี while true แต่ไม่มี Wait หรือไม่?
⑥ Native ที่เรียกหนักหรือไม่?
⑦ Cache ค่าได้หรือไม่?
⑧ Loop ผ่าน Entity/Player เยอะหรือไม่?
⑨ Thread ถูกสร้างซ้ำได้หรือไม่?
⑩ Thread ต้องมี Condition หยุดหรือไม่?
⑪ Query Database อยู่ใน Loop หรือไม่?
⑫ ส่ง Network Event จาก Loop ถี่เกินไปหรือไม่?
⑬ NUI Message ถูกส่งทุก Frameโดยไม่จำเป็นหรือไม่?
⑭ Profiler/Resmon แสดง Resource สูงหรือไม่?

Checklist นี้ช่วยลดปัญหา Script กิน CPU ได้มาก

⑥⓪ ตัวอย่าง CreateThread ที่เหมาะสำหรับมือใหม่

ลองสร้าง Command เปิด/ปิด Thread

local enabled = false

RegisterCommand(
    'togglecoords',
    function()

        enabled =
            not enabled

        print(
            'Coords thread:',
            enabled
        )
    end,
    false
)

CreateThread(function()
    while true do
        local sleep = 1000

        if enabled then
            sleep = 500

            local ped =
                PlayerPedId()

            local coords =
                GetEntityCoords(ped)

            print(
                ('x: %.2f y: %.2f z: %.2f')
                :format(
                    coords.x,
                    coords.y,
                    coords.z
                )
            )
        end

        Wait(sleep)
    end
end)

เมื่อ

enabled = false

Thread Sleep 1 วินาที

เมื่อเปิด

enabled = true

จะอ่าน Coordinates ทุกครึ่งวินาที

นี่เป็นตัวอย่างที่ช่วยเข้าใจทั้ง

CreateThread
while
Wait
State
Native
Adaptive Sleep

ใน Script เดียว

คำถามที่พบบ่อยเกี่ยวกับ FiveM CreateThread

FiveM CreateThread คืออะไร

คือ Function ของ CfxLua สำหรับสร้าง Thread/Coroutine ใหม่ให้ Code ทำงานแบบ Asynchronous ภายใต้ Scheduler ของ FiveM

Citizen.CreateThread กับ CreateThread ต่างกันไหม

CreateThread เป็นรูปแบบ Alias/ชื่อสั้นที่ใช้แพร่หลาย สามารถพบทั้งสองรูปแบบใน FiveM Lua Script

CreateThread ใช้ Client ได้ไหม

ได้ และพบมากใน Gameplay, Marker, Controls และ Native Loops

CreateThread ใช้ Server ได้ไหม

ได้ Citizen.CreateThread อยู่ใน Server-side Lua Functions ของ Cfx.re เช่นกัน

while true ต้องมี Wait ไหม

ต้องมี Yield ที่แน่นอน Cfx.re เตือนว่า Infinite Loop โดยไม่มี Wait() สามารถทำให้ Client Game ค้างหรือ Crash ได้

Wait(0) คืออะไร

Yield Coroutine จนถึง Game Tick ถัดไป

Wait(1000) คืออะไร

พัก Thread ประมาณ 1 วินาทีแล้วกลับมาทำงานต่อ

ทุก Thread ควรใช้ Wait(0) ไหม

ไม่ ใช้ Wait(0) เฉพาะ Logic ที่จำเป็นต้องทำทุก Game Tick/Frame

Marker ต้องใช้ Wait(0) ไหม

การ Draw Marker ต่อเนื่องมักต้องทำทุก Frameในช่วงที่ต้องแสดง แต่สามารถใช้ Adaptive Sleep เมื่อ Player อยู่ไกลเพื่อประหยัด CPU

CreateThread ทำให้ FPS ตกไหม

ได้ถ้า Thread มี Heavy Loop, Native Calls จำนวนมาก หรือทำงานทุก Frameโดยไม่จำเป็น

สามารถสร้างหลาย Thread ได้ไหม

ได้ แต่ไม่ควรสร้าง Thread จำนวนมากหรือสร้าง Infinite Thread ซ้ำทุกครั้งที่ Event/Command ถูกเรียก

Thread หยุดอย่างไร

ใช้ Condition เช่น while running do แล้วเปลี่ยน running = false หรือใช้ break

Wait ใช้นอก CreateThread ได้ไหม

Wait() ต้องทำงานใน Coroutine Context ที่รองรับ Yield เช่น Thread; Cfx.re ระบุว่า Citizen.Wait ใช้ Lua Coroutines

CreateThread กับ Event ต่างกันอย่างไร

Thread เหมาะกับงานต่อเนื่อง/เป็นช่วง ส่วน Event เหมาะกับการทำงานเมื่อเหตุการณ์เกิดขึ้น

CreateThread กับ SetTimeout ต่างกันอย่างไร

Thread เหมาะกับงานต่อเนื่อง ส่วน SetTimeout เหมาะกับงานครั้งเดียวหลัง Delay

CreateThread Query Database ได้ไหม

ทำได้ใน Server Logic แต่ไม่ควร Query Database ถี่ใน Loop โดยไม่มีเหตุผล

Resmon สูงเกี่ยวกับ CreateThread ไหม

เกี่ยวได้ โดยเฉพาะ Resource ที่มี Loop Wait(0), Native จำนวนมาก หรือสร้าง Threads ซ้ำ อย่างไรก็ตามต้องใช้เครื่องมือวัดเพื่อหาต้นเหตุจริง

สรุป FiveM CreateThread คืออะไร

FiveM CreateThread() คือ Function สำหรับสร้าง Thread/Coroutine ใหม่ใน CfxLua เพื่อให้ Script สามารถทำ Logic แบบต่อเนื่อง เป็นช่วงเวลา หรือรอ Operation โดยไม่ Block Flow หลักของ Resource

รูปแบบพื้นฐานคือ

CreateThread(function()
    while true do
        Wait(1000)

        -- logic
    end
end)

หลักที่สำคัญที่สุดคือ

while true
+
ไม่มี Wait
=
อันตราย

และ

Wait(0)
=
Game Tick ถัดไป

ไม่ได้หมายความว่า Thread ทุกตัวควรใช้ Wait(0)

ถ้า Logic ไม่จำเป็นต้องทำทุก Frame ควรใช้ Interval ที่สูงขึ้น หรือเปลี่ยนเป็น Event-driven Logic

สำหรับ Performance ที่ดีควรคิดตามลำดับ

ต้องมี Thread ไหม?
↓
ต้อง Loop ไหม?
↓
ต้องทุก Frameไหม?
↓
ลด Native Calls ได้ไหม?
↓
Cache ได้ไหม?
↓
ใช้ Adaptive Wait ได้ไหม?
↓
ใช้ Event แทนได้ไหม?

สำหรับผู้ที่กำลังเรียน FiveM Developer กับ comsiam การเข้าใจ CreateThread() อย่างถูกต้องจะช่วยป้องกัน Script ที่ดูเหมือนทำงานปกติแต่กิน Client CPU อยู่ตลอดเวลา

โดยเฉพาะ Resource ที่มี Marker, Controls, Vehicle Checks และ Nearby Interaction ซึ่งมักใช้ Thread จำนวนมาก การเลือก Wait() และ Lifecycle ให้เหมาะสมมีผลต่อ FPS และ Resmon อย่างชัดเจน

และบทความถัดไปของ comsiam จะเข้าสู่หัวข้อที่เกี่ยวข้องโดยตรงคือ FiveM Wait คืออะไร ใช้อย่างไรไม่ให้ Script กิน CPU ซึ่งจะเจาะ Wait(0), Milliseconds, Game Tick, Coroutine และการเลือก Sleep Time ให้เหมาะกับแต่ละระบบ

Comments

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด