topic proposal: differences/changes when you move to multithread

Forum for discussion about the documentation project.
speedfixer
Posts: 606
Joined: Nov 28, 2012 1:27
Location: CA, USA moving to WA, USA
Contact:

topic proposal: differences/changes when you move to multithread

Post by speedfixer »

Everyone WANTS to do it, but the learning curve seems too steep.
Using multithreading(MT) is certainly a different way to think, and can be a very difficult concept to correctly appreciate the changes needed.

Once simple code can seem to get 10 times more complicated. Many objections always thrown out about why NOT ... just ... aren't ... true.
(My biggest gripe: you only have 2 cores? Then MT won't help you. Then tell me this: how did we have all those single core, multiuser systems way back when? I sold a LOT of them! My customers were happy.)

What would help:
Better discussions of when and when not to use MT would help, not just parroting the usual textbook stuff. One discussion of when (for example. simply: you have enough speed and resources available); one discussion of when not (with todays systems, you HAVE to test to know); one discussion of the tradeoffs (complexity of code, halted processes, debugging, future scope/intent of the program/library you are writing), and more.

With any code examples held to the end, a practical discussion of what changes might be needed from single thread code to MT code. (I am seeing the 'by ref' discussion and hope the tutorial will at least touch on the changes most likely needed for MT.)

Actually, it isn't that hard once you appreciate then *when* as well as the *what* of your code.
I had/still have more trouble with remembering to update pointers than I do with code/data guards.

Especially on this topic, I know a few of the pros out there have to work hard to keep their mouth shut without inviting a firestorm or shutdown from a few of the high contributers. I don't know how to avoid that.

Eveyone runs into problems with user input and screen display. Each could be a seperate (but obviously related) topic.
Loop control (whether main controlling thread, or thread controlling main - actually, it MUST be both) is mentioned in forums, but not well treated.

While FreeBASIC can and should be simple for a beginner to learn, Its advanced features and speed allow for truely powerful programs. The tutorials should help everyone get there quicker. (And PLEASE: clear out/refresh some the very old, very stale, only barely correct old tutorials that new people look to and still can't get beyond what they knew *before* they looked at those old things. And empty still-under-contruction time wasteing links/topics.)

But I DO want FB programmers to be able to write pro-level programs. FB is capable of that. (Then, we can move to improving portablility, maybe. A topic on library inclusions for the different OS's? Maybe a seperate forum topic on required support libraries, how to acquire or avoid them? Windows is either a blessing or a curse with its 'all MS, all the time' approach.)

I REALLY like this more organized approach shown lately. I am grateful fxm has the time, energy, and interest to invest himself in writing these documents. I believe these will help move FB forward better AT THIS TIME than more compiler/system changes.

david
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: topic proposal: differences/changes when you move to multithread

Post by Tourist Trap »

Hi here,

not sure I'm not off-topic. If I'm so, please excuse me. So if not wrong about the place, I wanted to reference here a discussion we had long time ago dealing with multithreading with graphics. Here is some of what was said (by me)...
As a peasant, this is here what I need to know:

Can we do multithreading in FB? ---------->
  • yes it's an easy syntax based on THREADCREATE and a procedure with an inner loop ,
  • but not all commands, actions, keywords, etc... are avaiable without mutual exclusion mechanism
Can we do multithreading with graphics actions? ---------->
  • yes, but many commands are to be mutexed for many reasons, and some should be avoided for other reasons like flickering
What strategy suggested for multithreading graphics ? ----------->
  • using the main program with a main loop,
  • having the screen actualized once and once only in the whole loop cycle, including threads
  • having the main loop do the screen actualization, and the threads synchronisation, conditional call, or whatever control and management
  • for doing this, ScreenCopy strategy is ok with some synchrozitation via a waiter mechanism, or a CONDSIGNAL mechanism
  • ScreenLock/ScreenUnlock (good in general for single threading) is not to be priviledged here: each time ScreenUnlock is triggered the screen has to be refreshed and can cause flickering
What is cool with multithreading graphics ? ----------->
  • each thread can set an active screen of its own with SCREENSET, and also have one VIEWPORT, there is no conflict at this level, that's quite manageable
Of course it's perfectly unclear. It's not that simple to write an article!

Whole reference:
viewtopic.php?f=3&t=24937

Thanks.
Lost Zergling
Posts: 534
Joined: Dec 02, 2011 22:51
Location: France

Re: topic proposal: differences/changes when you move to multithread

Post by Lost Zergling »

I carried out in May June some tests on the lists using Mutex. I had to stop this way because of excessive performance decreases regardless of the method used. Maybe my programming was hugly and/or the multi threading was the best choice in this context. I would do other tests after enough progress on the multi threading but it seems that the OS shares the time allocated to the various programs, thus the main instance seems significantly slowed compared to two compiled code manually launched one after the other by the user or launched by a batch and interacting with each other in another way. Moreover, the control of threads also seems to consume the global resource : as a result, the program seems hardly faster in return for a huge complexity while mobilizing significantly more global resources. My point is not to criticize, I think that multi threading is very useful, but in the context of the list engine, I can not (yet?) validate the interest of doing it.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: topic proposal: differences/changes when you move to multithread

Post by jj2007 »

In principle a good idea. Maybe two (or more?) uses should be discussed:
1. more speed by using more than one core
2. prevent hanging of a Gui application by moving "blocking" stuff into a thread
caseih
Posts: 2157
Joined: Feb 26, 2007 5:32

Re: topic proposal: differences/changes when you move to multithread

Post by caseih »

In dealing with multithreaded programming and synchronization issues, it's not only helpful to know about mutexes, but there are also other very useful primitives such as semaphores (counting semaphores), and queues (which use semaphores). There are numerous classic principles and problems that illustrate problem solving with threads including things like the producer/consumer model, the dining philosophers problem, etc. All worth studying.

Any tutorial about multithreading needs a big disclaimer, though. There's an old saying that applies very well to multithreading: If you have a problem and think, oh I can solve this with threads, now you have two (or more) problems!

Threading is very fraught and prone to synchronization problems, race conditions, heisenbugs, deadlock, and starvation. Programmers enter at their own peril!

Threads can be a neat solution to a pressing problem, but the pitfalls need to be clearly stated. There are some best practices, such as have been mentioned, but they can't prevent all issues. Often, asynchronous I/O (Windows, Linux, Mac all support this) solves the problem without threads at all, particularly in a GUI loop.
speedfixer
Posts: 606
Joined: Nov 28, 2012 1:27
Location: CA, USA moving to WA, USA
Contact:

Re: topic proposal: differences/changes when you move to multithread

Post by speedfixer »

When threads become fundamental in your programming techniques, it seems almost an art when you try to explain it to a newcomer: there are SO MANY traps, pitfalls, caveats, blind spots and simple hard-to find errors that make the learning curve so very steep.

Perhaps thread learning can be divided up into several sub topics.

Possible topics and example:

Myths about threading.
First and foremost: the number of cores in your cpu has NO relationship to possible thread count. How many multi-thread/multi-user systems existed before multi-core cpus arrived? Of course, as is always true, more horsepower is more horsepower. Efficiency in thread usage is a separate topic.

OS/cpu issues.
Some manage or have thread capabilities different than others. (Wait until you battle thread-pool problems in MS to learn the hard way. Can have HUGE impact on actual performance.)

Usual, simple errors.
Failing to provide an exit strategy to the thread.

Very obscure thread problems.
(See OS topic above.) And, as always, incorrect mutex/lock/semaphore/conditional usage.

I/O management.
Most systems require isolation/segregation of I/O or I/O management to a single thread. (A single source explanation of this just for FB would probably help a lot of people learn threads quicker.)

Information passing.
How to get info into and out of a thread. Access to global data structures with guards. Internal and external state management.

When/how to use mutexes/conditionals.
Too many just gets in the way. Not enough and the wrong thread always reaches the finish line too early.

Each of these could be a topic of its own and would get lively discussion on 'the best' or 'the only' way it should be done. But someone with interest trying to learn would gain extra insight and a little more of the total picture if each of these topics (and others) could be examined in detail.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: topic proposal: differences/changes when you move to multithread

Post by fxm »

IMHO, multi-threading programming is a very difficult concept to master. Many bugs only appear in certain real-time conditions and are therefore very difficult to predict, identify and even more to correct.

I tried to help the user as I could, with my few knowledge and experiments, using two different axes of approach: Other axes of approach would necessarily improve the user's understanding of this difficult concept to implement.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: topic proposal: differences/changes when you move to multithread

Post by fxm »

fxm wrote:..........
- I added in the post #1 of this article a very short introduction to multi-threading:
fxm wrote:How to Manage a Critical Section of the code of a Thread in FB (post #1)

A critical section is a part of a multi-threading program that may not be concurrently executed by more than one of the program processes:
- It is a piece of a program that requires mutual exclusion of access.
- Typically, the critical section accesses a shared resource, such as a data structure, a peripheral device, or a network connection, that does not allow multiple concurrent accesses.

FB provides built-in support for multi-threaded programming. A multi-threaded program contains two or more parts that can run concurrently. Each part of such a program is called a thread, and each thread defines a separate path of execution.

When a program starts up, one thread already begins running immediately. This is usually called the "main" thread of the program, because it is the one that is executed when a program begins.
- It is the thread from which user may spawn other “child” threads (which in turn may spawn other "sub-child" threads).
- Often, it must be the last thread to finish execution because it performs various shutdown actions (as a "child" thread must also do so with respect to its eventual "sub-child" threads spawned).
- But other than that, it can also compete (with its own critical sections) with all other threads explicitly spawned by user.


The algorithm to ensure exclusive use of a critical section may be designed in a process either asynchronous or synchronous, which applies to the threads.
.....
- Example of very basic mechanism of a "main" thread spawning a "child" thread which in turn spawns a "sub-child" thread:

Code: Select all

Sub thread2 (Byval p As Any Ptr)              '' "sub-child" thread (#2)
  Print "   beginning thread2"
  Sleep 500
  For I As Integer = 1 To 9
    Print "    " & I
    Sleep 500
  Next I
  Print "   end thread2"
End Sub

Sub thread1 (Byval p As Any Ptr)              '' "child" thread (#1)
  Print " beginning thread1"
  Sleep 500
  Dim As Any Ptr p2 = Threadcreate(@thread2)
  Threadwait(p2)
  Sleep 500
  Print " end thread1"
End Sub

'Main code                                    '' "main" thread (#0)
Print "beginning thread0"
Sleep 500
Dim As Any Ptr p1 = Threadcreate(@thread1)
Threadwait(p1)
Sleep 500
Print "end thread0"

Sleep
speedfixer
Posts: 606
Joined: Nov 28, 2012 1:27
Location: CA, USA moving to WA, USA
Contact:

Re: topic proposal: differences/changes when you move to multithread

Post by speedfixer »

That code as an example of thread usage is EXCELLENT.

Simple, uncomplicated, zero extra 'noise' added to distract from the example.

I wish more sample code was like this.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: topic proposal: differences/changes when you move to multithread

Post by fxm »

Yes, but as soon as we have to manage with competition problems between the different threads, from the simple exclusion control (by mutex) up to the more complicated synchronization control (by mutex + conditional variable), the code becomes obligatorily more and more longer and more complex (see the various examples in documentation and in article).

If user has chosen to use multiple threads running in one same process (multi-threading), and not multiple processes of one thread each (multi-tasking), it is because he wants them to easily share common resources. Therefore competition problems between them are also to be dealt with.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: topic proposal: differences/changes when you move to multithread

Post by MrSwiss »

fxm wrote:Yes, but as soon as we have to manage with competition problems between the different threads, from the simple exclusion control (by mutex) ...
Another issue with threads is: how to 'communicate' with a thread, without a bunch of globals.

A Type comes to mind almost instantly, but we only have a single Any Ptr argument, in the
'worker'-procedure (by definition a Sub).
Like so: Sub thread_run(ByVal p As Any Ptr) which can't be changed!
(Examples usually fail, to explain the solution to that particular restriction, even if the code
contains it. By just doing it, without explanation of the importance, of this essential step.)

Needless to state, that pointer know-how is a requirement, to successfully deal with it. The
match deciding line in code, is the very first one: Dim As MyType Ptr pmt = p
(where the Any Ptr is converted to the Type's Ptr, which allowes access, to its members)
Without this step, the Type (and, its content) remains 'unknown territory' to the Sub ...

In the below example, a simple Time-thread (unsynchronized) is used, with a MUTEX:

Code: Select all

' Time_Thread_1.bas -- 2018-08-14, MrSwiss
'
' compile: -s console
'

' thread data-type (typedef struct in C-language)
Type thr_data                           ' a default initialized data-type (10 bytes)
    As ZString * 9  sTime = ""          ' actual payload (user data, the time)
    As Boolean      cont  = TRUE        ' thread procedure control (loop run/quit)
End Type

Dim As thr_data     dt1                 ' one instance of type, for this thread
Dim Shared As Any Ptr   MLock           ' define the common MUTEX (to all threads!)

' the threads 'worker' procedure (Sub with any ptr argument, is mandatory!)
Sub thr_run( ByVal pd As Any Ptr )      ' ptr 'pd', provides access to above type
    Dim As thr_data Ptr  ptd = pd       ' convert 'any ptr' to 'type ptr' !!! _
    ' the only way, to correctly access the internal members, of the type
    While ptd->cont                     ' run, until Boolean = FALSE (quit proc.)
        MutexLock(MLock)                ' lock mutex, as long as we process data
        If ptd->sTime <> Time Then
            ptd->sTime = Time           ' update time, when changed only!
        End If
        MutexUnLock(MLock)              ' free mutex (other thread can use it now)
        Sleep(100, 1)                   ' break (length defines time accuracy!)
    Wend                                ' 100 mS is sufficiently accurate and saves _
End Sub                                 ' a lot of resources, for other processes!

' get our thread up and running
MLock = MutexCreate()                   ' create common MUTEX (mutually exclusive)
Dim As Any Ptr  thread = ThreadCreate(@thr_run, @dt1)   ' create thread (one shot) 
If thread = 0 Then                      ' error check (no error = no action)
    Print "thread creation failed! ";   ' on error, inform user, then wait for _
    Beep : Sleep : End 1                ' user action --> error-quit
End If
' thread started successfully

' ===== start main =====
Dim As String   hi  = "Welcome, to FreeBASIC's threading ... ", _
                tth = "Time thread --> ", mth = "MAIN thread --> "
Dim As ULong    cnt = 0                 ' used as color changer

Width 80, 25 : Color(15, 0) : Cls       ' define standard console size and color

' print static messages, position cursor and switch it OFF
Locate(1, 57) : Print tth; : Locate 13, 1 : Print mth;
Locate(25, 1, 0) : Print "press a key, to EXIT ... ";

Do
    MutexLock(MLock)                    ' MAIN run's exclusive (no data change!)
    ' do any other mighty important stuff from here ... e.g. display update
    Color(12, 7) : Locate(1, 73)        ' hilight Time-thread output (red/grey)
    Print dt1.sTime : Locate(13, Len(mth) + 1)  ' output Time-threads data
    Color(9 + cnt, 0) : Print hi        ' this is just for visible test purposes
    ' finish mighty important stuff before this line ...
    MutexUnLock(MLock)                  ' release MAIN's lock (other thread enable)
    Sleep(50, 1)                        ' Time thread can run, from here
    cnt += 1 : If cnt > 6 Then cnt = 0  ' incr. counter | reset cnt if max. reached
Loop Until Len(InKey()) > 0             ' on user action --> exit program
' clean up ... in opposite order of creation! (thread first then mutex)
dt1.cont = FALSE : ThreadWait(thread)   ' stop thread proc., wait for thread stop                      ' 
MutexDestroy(MLock)                     ' delete mutex
Locate(,, 1) : Color(7, 0) : Cls        ' swith cursor ON, again & default colors
' ===== end main =====  ' ----- EOF -----
speedfixer
Posts: 606
Joined: Nov 28, 2012 1:27
Location: CA, USA moving to WA, USA
Contact:

Re: topic proposal: differences/changes when you move to multithread

Post by speedfixer »

Globals are like goto and BASIC: some programming GOD somewhere said they are evil and bad, and the 'educated' people teach that over and over. But at one time, every teacher told their students the earth was flat.

Mr. Torvalds has loudly disagreed with the opinion regarding that first term; we all here obviously disagree with the last.

You could bury your frequently-accessed data in procedures, but that just adds a lot of extra code, slows things down, will require rewrite after rewrite before your program is complete and just begs for hard to pinpoint errors.

Certainly, as soon as the amount of data to be passed between procedures is no longer small, you need to manage/guard that access. But if that data must be accessed by many different procedures, you will be making a lot of global flags, semaphores, mutexes, etc. anyway.

Globals aren't to be avoided. But once you pass about 70 lines or so - more than one page on your editor screen - you need to do some planning and organizing about how your program is going to work. Globals shouldn't be sprinkled about whenever/wherever you need to share some data.
If your program is complex, most data doesn't need to be seen by most of the rest of the system.

But I agree: discussing data passing methods would greatly help a newbie be more successful, quicker.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: topic proposal: differences/changes when you move to multithread

Post by fxm »

For my part, in the case of a common global mutex, I would prefer to declare it as a static data field of a common Type which is used to pass an instance pointer value to each user thread.

Referring to MrSwiss's example:

Code: Select all

Type thr_data                           ' a default initialized data-type (10 bytes)
    As ZString * 9  sTime = ""          ' actual payload (user data, the time)
    As Boolean      cont  = TRUE        ' thread procedure control (loop run/quit)
    Static As Any Ptr MLock             ' declare the common MUTEX (to all threads!)
End Type
Dim As Any Ptr thr_data.MLock           ' define the common MUTEX (to all threads!)
  • Syntax for accessing it:
    - 'thr_data.MLock' from the main thread
    - 'ptd->MLock' from the user thread
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: topic proposal: differences/changes when you move to multithread

Post by MrSwiss »

fxm wrote:... it as a static data field of a common Type which ...
What makes you assume, that the Type is common, too?
My assumption was, that the Type is custom to each thread (depending on it's job).
(two Time-threads hardly make a lot of sense)

This then, needs a Mutex implementation, as given (shared Any Ptr).
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: topic proposal: differences/changes when you move to multithread

Post by fxm »

MrSwiss wrote: What makes you assume, that the Type is common, too?
My assumption was, that the Type is custom to each thread (depending on it's job).
I would propose to use a common Type containing:
- the common fields for all threads,
- a pointer to a specific Type instance (containing the specific fields for the thread).
Example:

Code: Select all

Type thread_common_data
    Static As Any Ptr   MLock            ' declare the common MUTEX (to all threads!)
    As Any Ptr          TI               ' pointer to the specific Type instance for the thread
End Type
Dim As Any Ptr          thr_data.MLock   ' define the common MUTEX (to all threads!)

Type threadX_specific_data
    As Boolean          cont = TRUE      ' thread procedure control (loop run/quit)
    .....
End Type

Type threadY_specific_data
    As Boolean          cont = TRUE      ' thread procedure control (loop run/quit)
    .....
End Type

.....
Post Reply