06-01-2024, 10:19 PM
As I mentioned in my other threading post, I was going to attempt to Mandelbrot set in QB64 threaded.
Before anyone says that Mandelbrot doesn't need to be threaded and one thread is fast enough, you are correct. This is my journey learning about threads and how to actually use them. I wanted share some of what I learned and document some of it. So, if any of you other brave souls attempt this, you have a place to start. That is all for the TED talk.
I used a modified version of qbguy's implementation of Mandelbrot for my basis. That can be found in QB64-PE samples that Steve posted some time ago.https://qb64phoenix.com/forum/showthread...pe+samples
It needs two files to work. First is a header file named "mandelThread.h" The second is the actual basic program. You can name it "Mandelthread.bas" or whatever you like.
I've tried this on Linux, MacOS, and Windows. Windows needs a special consideration. Under Options --> Compiler Settings... --> C++ Compiler
Flags --> -pthread and under C++ Linker Flags --> --static.
![[Image: Screenshot-from-2024-06-01-16-44-19.png]](https://i.ibb.co/5vvsvsR/Screenshot-from-2024-06-01-16-44-19.png)
There are a few things to tinker with.
The header file.
The QB file.
Before anyone says that Mandelbrot doesn't need to be threaded and one thread is fast enough, you are correct. This is my journey learning about threads and how to actually use them. I wanted share some of what I learned and document some of it. So, if any of you other brave souls attempt this, you have a place to start. That is all for the TED talk.
I used a modified version of qbguy's implementation of Mandelbrot for my basis. That can be found in QB64-PE samples that Steve posted some time ago.https://qb64phoenix.com/forum/showthread...pe+samples
It needs two files to work. First is a header file named "mandelThread.h" The second is the actual basic program. You can name it "Mandelthread.bas" or whatever you like.
I've tried this on Linux, MacOS, and Windows. Windows needs a special consideration. Under Options --> Compiler Settings... --> C++ Compiler
Flags --> -pthread and under C++ Linker Flags --> --static.
![[Image: Screenshot-from-2024-06-01-16-44-19.png]](https://i.ibb.co/5vvsvsR/Screenshot-from-2024-06-01-16-44-19.png)
There are a few things to tinker with.
Code: (Select All)
'Are outside functions in worker thread allowed?
$LET THREADFUNC = TRUE
'Can the outside function have local variables?
$LET THREADFUNCLOCALVAR = TRUE
' Number of threads (1-32)
CONST cTHREADCOUNT = 32
' These affect actual calculation
CONST cREAL# = -2.0
CONST cIMAG# = -2.0
CONST cINCR# = 0.005
The header file.
Code: (Select All)
// mandelThread.h
// Threading Header
#include "pthread.h"
// needed the sleep()
#include <unistd.h>
// needed for clock()
#include <time.h>
#define MAXTHREADS 32
#define RETRYCOUNT 5
// Initialize Threads
static pthread_t thread[MAXTHREADS];
// Setup Mutexes
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// thread arguments
typedef struct thread_data {
int id;
} thread_data;
// Easy was to determine if a thread is running
static bool threadRunning[MAXTHREADS] = {false};
// QB's names for the threaded Subs
// You can locate these in your ''qb64pe/internal/temp'' folder.
// I found these in the 'main.txt'
void SUB_WORKER(int32*_SUB_WORKER_LONG_ID);
// wrap the subs so that you can easily get the void* for pthread
void* RunWorker(void *arg){
thread_data *tdata=(thread_data *)arg;
int id = tdata->id;
threadRunning[id] = true;
SUB_WORKER((int32*)&id);
}
// These are the commands that are accessed by you program
int invokeWorker(int id){
// setup arguments to be sent to the thread
thread_data tdata;
tdata.id = id;
// Threads are not always successfully started
// So, we retry until it sticks or we exceed the retry count.
int retry_count = 0;
// Is it already running?
if (!threadRunning[id]) {
// try to start thread
while(pthread_create( &thread[id], NULL, RunWorker, (void *)&tdata))
{
// thread is not running
if (retry_count++ > RETRYCOUNT){
// jumpout if tried more than RETRYCOUNT
return threadRunning[id];
}
// wait a sec before trying again
sleep(1);
};
// Give thread a sec to spool up
int timeout_time = clock() * 1000 / CLOCKS_PER_SEC + 1000;
do {} while (clock() * 1000 / CLOCKS_PER_SEC <= timeout_time && threadRunning[id] != true);
}
return threadRunning[id];
}
void joinThread(int id){
pthread_join(thread[id],NULL);
threadRunning[id] = false;
}
void exitThread(){
pthread_exit(NULL);
}
void lockThread(){
pthread_mutex_lock(&mutex);
}
void tryLockThread(){
while (pthread_mutex_trylock(&mutex) != 0) {};
}
void unlockThread(){
pthread_mutex_unlock(&mutex);
}
void lockDestroy(){
pthread_mutex_destroy(&mutex);
}
Code: (Select All)
' Header interface $CHECKING:OFF DECLARE LIBRARY "./mandelThread" FUNCTION invokeWorker (BYVAL id AS LONG) ' start worker thread SUB joinThread (BYVAL id AS LONG) ' wait til thread is finished SUB exitThread ' must be called as thread exits SUB lockThread ' mutex lock SUB unlockThread ' mutex unlock SUB tryLockThread ' keep trying to lock thread SUB lockDestroy END DECLARE 'Are outside functions in worker thread allowed? $LET THREADFUNC = TRUE 'Can the outside function have local variables? $LET THREADFUNCLOCALVAR = TRUE _TITLE "Threaded Mandelbrot" SCREEN _NEWIMAGE(800, 800, 32) ' Number of threads (1-32) CONST cTHREADCOUNT = 32 ' These affect actual calculation CONST cREAL# = -2.0 CONST cIMAG# = -2.0 CONST cINCR# = 0.005 TYPE tWORKER xStart AS LONG yStart AS LONG xSize AS LONG ySize AS LONG xEnd AS LONG yEnd AS LONG x AS LONG y AS LONG r AS DOUBLE xt AS DOUBLE yt AS DOUBLE xx AS DOUBLE yy AS DOUBLE real AS DOUBLE imag AS DOUBLE incr AS DOUBLE colour AS DOUBLE sc AS LONG img AS _MEM offset AS _OFFSET command AS _BYTE END TYPE 'Initialize resources DIM SHARED AS tWORKER mWorker(cTHREADCOUNT) DIM SHARED AS LONG argId DIM AS LONG indx DIM ky AS STRING 'Setup workers initial values FOR indx = 0 TO cTHREADCOUNT - 1 initWorker indx NEXT 'Start the workers FOR indx = 0 TO cTHREADCOUNT - 1 IF invokeWorker(indx) = 0 THEN PRINT "Failed to start worker thread!": END NEXT ' Main loop DO ' handle user input ky = INKEY$ IF ky = CHR$(27) THEN FOR indx = 0 TO cTHREADCOUNT - 1 'set the workers command to quit mWorker(indx).command = -1 ' wait for the thread to quit joinThread id NEXT ' cleanup the lock lockDestroy SYSTEM END IF ' display workers work FOR indx = 0 TO cTHREADCOUNT - 1 ' lock the images so that they can be displayed ' in the case of an image it may not be strictly necessary, but ' data that needs to be correct it does. lockThread _PUTIMAGE (mWorker(indx).xStart, mWorker(indx).yStart), mWorker(indx).sc unlockThread NEXT ' keeping the framerate slow so that workers have more access to the image. _LIMIT 5 _DISPLAY LOOP SYSTEM 'Work is split amongst the threads in horizontal bands. SUB initWorker (id AS LONG) mWorker(id).xSize = _WIDTH mWorker(id).ySize = _HEIGHT / cTHREADCOUNT mWorker(id).xStart = 0 mWorker(id).yStart = id * mWorker(id).ySize mWorker(id).xEnd = mWorker(id).xStart + mWorker(id).xSize mWorker(id).yEnd = mWorker(id).yStart + mWorker(id).ySize mWorker(id).incr = cINCR mWorker(id).real = cREAL mWorker(id).imag = cIMAG + mWorker(id).yStart * mWorker(id).incr mWorker(id).sc = _NEWIMAGE(mWorker(id).xEnd, mWorker(id).yEnd, 32) mWorker(id).img = _MEMIMAGE(mWorker(id).sc) END SUB '################################################################################################### ' Threaded Subs '################################################################################################### SUB worker (id AS LONG) ' Worker will continue to loop until mWorker(id).command is true DO mWorker(id).y = 0: DO mWorker(id).r = mWorker(id).real mWorker(id).x = 0: DO ' Decide on how the thread is going to run ' THREADFUNC determines if the calculation of the Mandelbrot will use a function or not ' THREADFUNCLOCALVAR determines if the function uses local variables $IF THREADFUNC = TRUE THEN lockThread argId = id $IF THREADFUNCLOCALVAR = TRUE THEN mandelA $ELSE mandelT $END IF unlockThread $ELSE mWorker(id).xt = mWorker(id).r: mWorker(id).yt = mWorker(id).imag mWorker(id).colour = 256.0: Do mWorker(id).xx = mWorker(id).xt * mWorker(id).xt: mWorker(id).yy = mWorker(id).yt * mWorker(id).yt If mWorker(id).xx + mWorker(id).yy >= 4 Then Exit Do mWorker(id).yt = mWorker(id).xt * mWorker(id).yt * 2 + mWorker(id).imag mWorker(id).xt = mWorker(id).xx - mWorker(id).yy + mWorker(id).r mWorker(id).colour = mWorker(id).colour - 1: Loop While mWorker(id).colour > 1 $END IF ' calculate offset based on the x and y cooridates mWorker(id).offset = mWorker(id).img.OFFSET + (mWorker(id).x * 4) + (mWorker(id).y * mWorker(id).xSize * 4) lockThread ' draw colors to the image _MEMPUT mWorker(id).img, mWorker(id).offset + 0, mWorker(id).colour AS _UNSIGNED _BYTE _MEMPUT mWorker(id).img, mWorker(id).offset + 1, mWorker(id).colour AS _UNSIGNED _BYTE _MEMPUT mWorker(id).img, mWorker(id).offset + 2, mWorker(id).colour AS _UNSIGNED _BYTE _MEMPUT mWorker(id).img, mWorker(id).offset + 3, 255 AS _UNSIGNED _BYTE unlockThread mWorker(id).r = mWorker(id).r + mWorker(id).incr mWorker(id).x = mWorker(id).x + 1: LOOP UNTIL mWorker(id).x >= mWorker(id).xEnd mWorker(id).imag = mWorker(id).imag + mWorker(id).incr mWorker(id).y = mWorker(id).y + 1: LOOP UNTIL mWorker(id).y >= mWorker(id).yEnd 'reset worker back to its original position mWorker(id).real = cREAL mWorker(id).imag = cIMAG + mWorker(id).yStart * mWorker(id).incr LOOP UNTIL mWorker(id).command ' This allows the thread to be joined at the end exitThread END SUB '################################################################################################### ' Threaded helper subs '################################################################################################### ' Mandelbrot with no local variables SUB mandelT mWorker(argId).xt = mWorker(argId).r: mWorker(argId).yt = mWorker(argId).imag mWorker(argId).colour = 256.0: DO mWorker(argId).xx = mWorker(argId).xt * mWorker(argId).xt: mWorker(argId).yy = mWorker(argId).yt * mWorker(argId).yt IF mWorker(argId).xx + mWorker(argId).yy >= 4 THEN EXIT DO mWorker(argId).yt = mWorker(argId).xt * mWorker(argId).yt * 2 + mWorker(argId).imag mWorker(argId).xt = mWorker(argId).xx - mWorker(argId).yy + mWorker(argId).r mWorker(argId).colour = mWorker(argId).colour - 1: LOOP WHILE mWorker(argId).colour > 1 END SUB ' Mandelbrot with local variables SUB mandelA DIM AS DOUBLE x, y, xx, yy, c x = mWorker(argId).r: y = mWorker(argId).imag FOR c = 256.0 TO 1 STEP -1 xx = x * x: yy = y * y IF xx + yy >= 4 THEN EXIT FOR y = x * y * 2 + mWorker(argId).imag x = xx - yy + mWorker(argId).r NEXT mWorker(argId).colour = c END SUB
![[Image: Screenshot-from-2024-06-01-17-14-24.png]](https://i.ibb.co/0GM4XFg/Screenshot-from-2024-06-01-17-14-24.png)
2D physics engine https://github.com/mechatronic3000/fzxNGN
Untitled Rouge-like https://github.com/mechatronic3000/Untitled-Rougelike
QB Pool https://github.com/mechatronic3000/QBPool
Untitled Rouge-like https://github.com/mechatronic3000/Untitled-Rougelike
QB Pool https://github.com/mechatronic3000/QBPool