Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Multi Threading
#1
I know some of you might say - just use timers and gosub routines
but that's not multi-threading

I know qb64 is a single threaded language,
but I have an idea.
We can introduce some subroutines like the Sub _GL
example:
Code: (Select All)
Sub _Thread1
End Sub
Sub _Thread2
End Sub

I just don't know how to implement this from libqb
Reply
#2
It's not possible without significant changes to the runtime, the runtime is not designed to be thread-safe so there's many operations that will break if they happen across multiple threads (Ex. Using strings, creating/freeing images, reading input, etc.).

_GL gets around this by pausing execution of the main thread while the _GL thread is working, ensuring there's never two threads running your QB64 code at the same time.
Reply
#3
Ok.
But, is there a way I can try that myself?

I opened the code, but I can't figure out anything. So, I turned here.
Reply
#4
There's no easy way to try it because it will just blow up.

If you do want to try it you can use a C++ library to create threads via `DECLARE LIBRARY` (and probably some helper C++ code), then call back into your QB64 code from them. I'm not going to go in-depth on it though because it's not useful.
Reply
#5
(10-17-2025, 02:11 PM)DSMan195276 Wrote: There's no easy way to try it because it will just blow up.

If you do want to try it you can use a C++ library to create threads via `DECLARE LIBRARY` (and probably some helper C++ code), then call back into your QB64 code from them. I'm not going to go in-depth on it though because it's not useful.

Ok
I will try the pthreads library

Thanks

Update:
Not working, because pthreads need a pointer to a function, which is inaccessible in qb64.
Reply
#6
Despite what others have said, I have had some success at threading in QB64. 

https://qb64phoenix.com/forum/showthread.php?tid=2739

First, I have never tried it with OpenGL. 

Keep in mind that you are extremely limited on what you can do. A lot of QB64 functionality will be closed off to the child threads and possibly the main thread. Subroutines and Functions will also be problematic if multiple threads are trying to access them simultaneously. Lots of pitfalls, and not much help to be had. 

Don't get me wrong I'm not discouraging you. Just keep your expectations realistic.

I suggest looking over the code I posted, run the code, try to understand the code. 

I'm not sure what platform you are on, but on windows I had to add '-pthread' to compiler settings and '--static' to the linker settings.
Reply
#7
quite honestly.  if you want to mess around with threads.  you will have to program in something else other than qb64 phoenix.

like the others said.  regardless of what operating system you would want to run your program in.  there will be race conditions.  one service trying to step over all others.  trying to be first.  in setting itself up and/or having access to ram, disk space and other resources.

that's why string access is inherently not thread-safe.  this is a killer.  for many people who like basic's convenience with string handling.  that's why a programming language cannot be used neither which employs garbage collection.  such as lua.  it's harmful for music-production application for example.  that's why a complex program like reaper.  crashes occasionally.  there's just too much to keep track of.  while needing to keep things in tight synchronization.  if such a demanding a program had to make sure things were thread-safe.  it would just come to a grinding halt.  ("mutex lock" is an essential part of keeping track of thread execution.)  a bit better than that.  it could freeze at specific intervals.  which is very irritating.  many people have rejected linux distributions because of it.

in the least.  in some programming system implementations i've seen.  the "thread" has to be an entire function.  which relies only on global variables.  cannot even accept arguments.  no local variables of any kind.  the stack for such a function.  either will not exist or will have to be protected.

the subject should be read about extensively.
Reply
#8
As stated in the opening post you can use timers when that is enough.
Ive used sometines
Code: (Select All)
  Dim thread(0 To 9) As Integer
  For t% = 0 To 9: thread(t%) = _FreeTimer: Next t%
  On Timer(thread(0), .01) processRequest 0
  On Timer(thread(1), .01) processRequest 1
  On Timer(thread(2), .01) processRequest 2
  On Timer(thread(3), .01) processRequest 3
  On Timer(thread(4), .01) processRequest 4
  On Timer(thread(5), .01) processRequest 5
  On Timer(thread(6), .01) processRequest 6
  On Timer(thread(7), .01) processRequest 7
  On Timer(thread(8), .01) processRequest 8
  On Timer(thread(9), .01) processRequest 9
  For t% = 0 To 9: Timer(thread(t%)) On: Next t%

Another option when I really wanted to run several (long running) process in parallel was to use
Code: (Select All)
Shell -DONTWAIT
on several separate executables
It's not much, but it can work in some cases
45y and 2M lines of MBASIC>BASICA>QBASIC>QBX>QB64 experience
Reply
#9
Multithreading can be done, but it is complex. Here is some code to get you started on Windows, if you still wish to do so:

.h   threadwin.h (Size: 278 bytes / Downloads: 14)
Code: (Select All)
#include<strsafe.h>
int32 FUNC_MYTHREADFUNCTION(ptrszint*_FUNC_MYTHREADFUNCTION_OFFSET_LPPARAM);
extern "C"{
    __declspec(dllexport) int32 MyThreadFunction(ptrszint*lpParam){
        return FUNC_MYTHREADFUNCTION((lpParam));
    }
}

int32 sizeoftchar(){
    return sizeof(TCHAR);
}

.bas   threading.bas (Size: 5.94 KB / Downloads: 12)
Code: (Select All)
Option _Explicit
$Console:Only

Type MyData
    As Long val1, val2
End Type

Const BUF_SIZE = 255
Const MAX_THREADS = 20
Const HEAP_ZERO_MEMORY = &H00000008
Const INFINITE = 4294967295
Const STD_OUTPUT_HANDLE = -11
Const INVALID_HANDLE_VALUE = -1

Const MB_OK = 0

Const FORMAT_MESSAGE_ALLOCATE_BUFFER = &H00000100
Const FORMAT_MESSAGE_FROM_SYSTEM = &H00001000
Const FORMAT_MESSAGE_IGNORE_INSERTS = &H00000200
Const LANG_NEUTRAL = &H00
Const SUBLANG_DEFAULT = &H01

Const LMEM_ZEROINIT = &H0040

Declare CustomType Library
    Function LoadLibrary%& (lpLibFileName As String)
    Function GetProcAddress%& (ByVal hModule As _Offset, lpProcName As String)
    Sub FreeLibrary (ByVal hLibModule As _Offset)
    Function GetLastError& ()
    Function HeapAlloc%& (ByVal hHeap As _Offset, Byval dwFlags As Long, Byval dwBytes As _Offset)
    Function GetProcessHeap%& ()
    Sub ExitProcess (ByVal uExitCode As _Unsigned Long)
    Function CreateThread%& (ByVal lpThreadAttributes As _Offset, Byval dwStackSize As _Offset, Byval lpStartAddress As _Offset, Byval lpParameter As _Offset, Byval dwCreationFlags As Long, Byval lpThreadId As _Offset)
    Function WaitForMultipleObjects& (ByVal nCount As Long, Byval lpHandles As _Offset, Byval bWaitAll As _Byte, Byval dwMilliseconds As Long)
    Sub WaitForMultipleObjects (ByVal nCount As Long, Byval lpHandles As _Offset, Byval bWaitAll As _Byte, Byval dwMilliseconds As Long)
    Sub CloseHandle (ByVal hObject As _Offset)
    Sub HeapFree (ByVal hHeap As _Offset, Byval dwFlags As Long, Byval lpMem As _Offset)
    Sub StringCchPrintf Alias "StringCchPrintfA" (ByVal pszDest As _Offset, Byval cchDest As _Offset, pszFormat As String, Byval arg1 As Long, Byval arg2 As Long)
    Sub StringCchPrintf2 Alias "StringCchPrintfA" (ByVal pszDest As _Offset, Byval cchDest As _Offset, pszFormat As String, lpszFunction As String, Byval error As Long, Byval lpMsgBuf As _Offset)
    Sub StringCchLength Alias "StringCchLengthA" (ByVal psz As _Offset, Byval cchMax As _Offset, Byval pcchLength As _Offset)
    Function GetStdHandle%& (ByVal nStdHandle As Long)
    Function CreateMutex%& Alias "CreateMutexA" (ByVal lpMutexAttributes As _Offset, Byval bInitialOwner As Long, Byval lpName As _Offset)
    Sub WriteConsole (ByVal hConsoleOutput As _Offset, Byval lpBuffer As _Offset, Byval nNumberOfCharsToWrite As Long, Byval lpNumberOfCharsWritten As _Offset, Byval lpReserved As _Offset)
    Sub FormatMessage Alias FormatMessageA (ByVal dwFlags As Long, Byval lpSource As Long, Byval dwMessageId As Long, Byval dwLanguageId As Long, Byval lpBuffer As _Offset, Byval nSize As Long, Byval Arguments As _Offset)
    Sub MessageBox Alias "MessageBoxA" (ByVal hWnd As _Offset, Byval lpText As _Offset, lpCaption As String, Byval uType As _Unsigned Long)
    Sub LocalFree (ByVal hMem As _Offset)
    Function LocalAlloc%& (ByVal uFlags As _Unsigned Long, Byval uBytes As _Unsigned _Offset)
    Function lstrlen& Alias "lstrlenA" (ByVal lpString As _Offset)
    Function LocalSize%& (ByVal hMem As _Offset)
End Declare

Declare Library "threadwin"
    Function sizeoftchar& ()
End Declare

Declare Library
    Function MAKELANGID& (ByVal p As Long, Byval s As Long)
End Declare

Dim As _Offset libload: libload = LoadLibrary(Command$(0))
Dim As _Offset MyThreadFunc: MyThreadFunc = GetProcAddress(libload, "MyThreadFunction")

Dim As MyData pDataArray(1 To MAX_THREADS)
Dim As Long dwThreadIdArray(1 To MAX_THREADS)
Dim As _Offset hThreadArray(1 To MAX_THREADS), heap(1 To MAX_THREADS)

Dim As _Offset ghMutex: ghMutex = CreateMutex(0, 0, 0)
If ghMutex = 0 Then
    ErrorHandler "CreateMutex"
End If
Dim As Long i
For i = 1 To MAX_THREADS
    heap(i) = HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, Len(pDataArray(i)))
    Dim As _MEM pdata: pdata = _MemNew(8)
    _MemPut pdata, pdata.OFFSET, heap(i)
    _MemGet pdata, pdata.OFFSET, pDataArray(i)
    If heap(i) = 0 Then
        ExitProcess 2
    End If
    pDataArray(i).val1 = i
    pDataArray(i).val2 = i + 100
    hThreadArray(i) = CreateThread(0, 0, MyThreadFunc, _Offset(pDataArray(i)), 0, _Offset(dwThreadIdArray(i)))
    If hThreadArray(i) = 0 Then
        ErrorHandler "CreateThread"
        ExitProcess 3
    End If
Next
WaitForMultipleObjects MAX_THREADS, _Offset(hThreadArray()), 1, INFINITE
For i = 1 To MAX_THREADS
    CloseHandle hThreadArray(i)
    If heap(i) <> 0 Then
        HeapFree GetProcessHeap, 0, heap(i)
    End If
Next
CloseHandle ghMutex
_MemFree pdata
FreeLibrary libload

Function MyThreadFunction& (lpParam As _Offset)
    Dim As String * BUF_SIZE msgBuf
    Dim As _Offset hStdout
    Dim As Long cchStringSize, dwChars
    Dim As MyData MyData
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
    If hStdout = INVALID_HANDLE_VALUE Then
        MyThreadFunction = 1
    End If
    Dim As _MEM PMYDATA: PMYDATA = _MemNew(8)
    _MemPut PMYDATA, PMYDATA.OFFSET, lpParam
    _MemGet PMYDATA, PMYDATA.OFFSET, MyData
    StringCchPrintf _Offset(msgBuf), BUF_SIZE, "Parameters = %d, %d" + Chr$(10) + Chr$(0), MyData.val1, MyData.val2
    StringCchLength _Offset(msgBuf), BUF_SIZE, _Offset(cchStringSize)
    WriteConsole hStdout, _Offset(msgBuf), cchStringSize, _Offset(dwChars), 0
    _MemFree PMYDATA
    MyThreadFunction = 0
End Function

Sub ErrorHandler (lpszFunction As String)
    Dim As _Offset lpMsgBuf, lpDisplayBuf
    Dim As Long dw: dw = GetLastError
    FormatMessage FORMAT_MESSAGE_ALLOCATE_BUFFER Or FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS, 0, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), _Offset(lpMsgBuf), 0, 0
    lpDisplayBuf = LocalAlloc(LMEM_ZEROINIT, (lstrlen(lpMsgBuf) + lstrlen(_Offset(lpszFunction)) + 40) * sizeoftchar)
    StringCchPrintf2 lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeoftchar, "%s failed with error %d:" + Chr$(10) + " %s" + Chr$(0), lpszFunction + Chr$(0), dw, lpMsgBuf
    MessageBox 0, lpDisplayBuf, "Error" + Chr$(0), MB_OK
    LocalFree lpMsgBuf
    LocalFree lpDisplayBuf
End Sub

Note: The code is a few years old at this point, I think. It is based on this MSDN threading example.

Also, you mentioned pointers to functions. I can help you with that. The code above uses pointers to QB64 functions. Once you know how to do that, it's very simple.
The noticing will continue
Reply


Forum Jump:


Users browsing this thread: