My last three programming hints for today. For this year.
8. Multi-Threading, be Multi-Careful
As
mentioned earlier, only enter this domain if you know what you’re doing, and
don’t enter it if you don’t need to. If you want to learn a bit more about
Multi-Threading, read this, and a lot of other practical strategies, because you'll need them.
I consider
MT as a necessary evil. In Tower22, that uses a roaming world (thus not
level1,2,3, but just one big world, streamed around the player), it would be
unacceptable if load-operations would stall the game all the time. So they are
performed in background threads mostly. But boy, they can produce ugly code. Be
extremely careful when variables are shared between two or more threads,
because they can give you serious killer-bugs. For instance, I should not try
to draw parts of the world that are still somewhere halfway in their loading
(or unloading) process.
Instead,
try to think in terms of tasks. Determine what kind of tasks lend themselves to
be worked out isolated, in parallel. A Server makes a nice example. Maybe we
have 1 client, maybe 10. Or maybe 10-thousand. A client would typically ask
stuff. What is my position? Can you insert this new password? Please return me www.brazilianfartporn.com/index.html (don't click),
and so on. Now does your town-hall have a single ticket-window, queuing up all
those people? Or do you open a new window for every person that walks in? In
reality, option B is not feasibly unfortunately, but in a Virtual World we can.
A single
ticket-window sucks. Everybody is biting his lips when the little Korean woman keeps
saying yes-yes-yes, but doesn’t understand the airport lady telling she can’t
bring in boiled dog embryos. The drunk Brittish chap has enough of it, and
starts throwing excrement. And you realize you left your ID in the fucking car,
so, back-in-line. Same with computer stuff. We don’t want
clients waiting on each other, especially not if they are making havoc, or are timing-out.
It makes perfect sense to treat each client-connection as a separate thread, a separate
pipe. And if it breaks, the others stay intact.
I wasn't allowed to use the Puppy photo for MultiThreading, so; a bunch of fine gentlemen that understood parallel working. Except the lady in the foreground.
Network
clients lend themselves for multithreading because A: we have to, and B: tasks
have little in common – usually. I don’t care what my neighbour does on the
internet. His login, logout, his website queries or whatsoever, can be done
completely isolated from my connection. Unless, we’re playing a game together.
Now some server has to share data. If the neighbour and I are playing online
Chess, it would be pretty useful to see his latest move for instance.
Well
kids, the key to successful MultiThreading is too complicated to trash within the next few lines, but at least some here hints. And other than that, read & learn from books or the internet.
·
Use
semaphores or mutexes whenever shared variables need to be read or written
o
Or
use “Atomic” datatypes / instructions – with care.
o
Bools
are often atomic types, so they can be used as flags (“flag_IsReading”, “flag_Done”,
…)
·
And
better, minimize the amount of shared variables
o
And
vice-versa, minimize the amount of calls that can change states / (global)
variables.
o
That
also counts for single-threaded programs!
o
So
if data gets screwed up, you know where it comes from.
·
Threadsafe
LockedLists are useful
o
Use
them to put tasks in a queue. Typically the main thread would feed this list.
o
Another
thread could lock the list, pick a task from it, unlock the list, and perform
that task.
o
And
eventually report its results back via another locked list.
·
Extra
danger when working with variables that might be destroyed in the meanwhile
o
For
example, my A.I. bot scans the world, but at the same time the world is
unloaded as the player leaves it.
o
Make
sure you don’t clean up garbage if threads are still potentially working on it.
·
It
can be useful to make a copy of your variables to work with in a background
thread, and then synchronize back when the thread is done with them
·
If
your code starts stinking because of confusing flags, patch on top of patch,
and tricks to workaround… Better redo it. Trust me.
If your
MultiThreading implementation seems to be overly confusing, chances are you
didn’t design it very well. Like performance, you can’t just rip out a random
chunk of code, and move it to another thread. It needs to be in the design at
forehand. And as other hints keep saying here, keep it easy. If the reasons
& hows behind your MultiThreaded tasks are simple to explain, you probably
have a better time implementing them as well. Read about it.
9. Don’t blame your computer. Even if the
bastard did it
Yes, like
anybody else I have fail-days and moments that I wonder why I just didn’t
become a garbage man, instead of debugging impossible bugs. The 19 inch monitor
sure suffered some domestic violence when I was still a teenager.
But
being a few million bugs further, I learned that I had to blame myself in 99,9%
of the cases. Rather than smashing your machine, calling C++ an asshole,
cursing Bill Gates and the writers of libraryX, telling it’s all the fault of
those retarded n00b end-users, and swearing that the bug just cannot be true,
calm down. Cup of coffee, cigarette, calm down. And learn how to fix bugs, or
even better, how to avoid them.
Just
like you can’t avoid stinky flies in your house, you can’t avoid software-bugs completely
either. But at least you can reduce them by not throwing poop or leaving
rotting meat on the kitchen counter:
·
As
mentioned with Multi-Threading or making unnecessary libraries back in topic #7,
avoid advanced techniques if you don’t need them.
·
ALWAYS
work neat. Do not litter your code with hacks and “quickly try this for fun”
o
And
if you do override with a TEST, make it very clear so you can’t forget to
remove it later on
·
Do
not write the same variable or state via a dozen different callers / functions
/ directions.
·
If
you have memory profilers or leak-detectors, use them once in a while.
·
Do
not keep ignoring the same bugs for too long while you keep on adding other
code
·
Try
Catch & useful error messages/reports/dumps.
o
Do
not assume user-input or your file-loader will always work, because some
douchebag eventually will feed your program a rotten carcas.
·
Same
thing for anything communication related. Expect noise, expect disconnects,
expect chaos.
·
Robust
code. Proper naming, small to-the-point functions and using “field-tested”
methods.
o
As
handy and flexible as it may seem at first, don’t write 100 different ways to
do the same thing
And when
things do go wrong, start with Readable code. Because it’s easier to debug when
things do crash. Next, develop a sixth sense for errors. Don’t give up too
easily. Use all the artillery you got. Your best friend is the Debugger,
standard for most IDE’s, and in most cases, very useful to pinpoint and zoom-in
on the target:
·
Call
– Stack / Trace
o
As
the program halts or crashes, it shows the functions called earlier that
brought you to this place. Maybe the function itself is not faulty, but the
parameters given by its caller are?
·
Use
Breakpoints to halt the program when reaching a tagged line.
o
Do
we even get there at all? If not… decision gone wrong / bug earlier?
·
Step
through your code, line by line
o
See
on what exact line things go BOOM
o
Keep
a close eye on all your variables.
o
Weird
numbers? Wrong array indexing? Pointers Zeroed or never initialized at all?
·
You
can use “Watches” to monitor (global) variables over a longer time
o
Sometimes
global variable X may lead to a crash, but was actually mutilated by some other
process earlier on.
Also
don’t forget you can dump text lines or variables into a console in most cases.
With this, you can tackle most errors. There are a few son-of-a-bitch
exceptions though. So, first of all, do not suffer from
police-investigation-tunnel-vision. Stay open for trouble-causers in unexpected
corners… Which is why I warned you for Multi-Threading and using external
libraries, as they can hide certain errors.
Worst
thing are invalid memory reads/writes. This usually happens when forgetting to
initialize pointers, or when they refer to something already destroyed. Also
streaming larger data arrays and getting out of boundaries can affect innocent
surrounding variables. Such errors are mostly detected right away, but in some
cases your program keeps running and triggers a chain reaction of weirdness,
Twilight Zone errors. The type of error that seems untraceable and illogical.
But don’t forget, there is always a cause. Check your pointers. Or more
drastically, quarantine code sections by leaving them out (if possible). Also
memory profilers can be valuable here.
Oh, and
one more thing, don’t suffer from tunnelvision either (debuggers and the tips
above don’t show everything!!). If the police investigation doesn’t reveal
anything, try to approach the crime-scene from a whole different approach. For
example, sometimes bugs get fixed by catching another, seemingly unrelated,
bug.
If all
fails, have a plan-B. And no, crying is not a plan-B. Replace the faulty part
with other code, or if your whole procedure was wonky anyway, redo (see
lesson1). As much as it sucks, it’s part of the job.
10. Be
consistent
One more
hint, randomly grabbed from my stinky programming hat. Be consistent. In
everything you do. As you think, as you talk, as you walk, as you make your
sandwiches, as you program. As mentioned somewhere earlier, it’s good to view
your code from a third perspective. You probably think your code rocks, but
would another programmer think the same? Probably not, because programmers are stubborn
and can always do better than you. But… at least it helps if your code A: just
works, and B: is comprehensible.
As
explained in #5, Size matters. What also matters, is consistency. If you look
carefully at other professional libraries, you will notice there are certain
naming conventions, as well as a certain approach. OpenGL for instance is a working
on a State Machine principle. In general you would:
·
Bind
the resources you want to work on or with (textures, vertex buffers, shaders,
…)
·
Toggle
options on/off (enable depth-testing, disable anti-alias, …)
·
Eventually
issue a drawing-command
·
Bind
to some other resource
You may
argue if that’s easy when comparing to other API’s such as DirectX, but the
point is that they use this approach everywhere. So you know what to expect.
Things would be very confusing if a mixture of techniques was used. If textures
would be OOP classes while vertex-buffers aren’t, it makes it harder to learn
the patterns of your API. If you have to toggle off State Machine options after
using X, but not after doing Y, it generates false expectations and errors.
Providing
good documents is Silver, providing self-explanatory code that doesn’t need a
manual, is Gold. Certainly some things are just too complicated, but at least
try to.
Styles
changes though. In the eighties we had big-fat mullets, in 2016 kids grow
beards to look more manly, but spend more hours behind the mirror than their
girls, combing and twisting that beard. Next year I expect pipe-smoking, sabre
duels, and Nazi moustaches to be the trend again. Your coding style will change
too. And that makes it harder to work consistently, especially when working on
a large project spread over months/years, or done with multiple persons. You
can write down coding conventions, though I’m the type of guy that would never
read such a document again. In that case, keep looking back at other previously
done code segments to take an example, instead of just writing away blindly.
And even if you’re new style is much cooler than those old snippets, think
again before suddenly doing things differently in the same code-base.
Be neat.
If not for another programmer, then at least for yourself, in case that bitch
from the office calls again with a weird problem, four years later. Do yourself
a favour. It doesn’t cost too much, and that neater style you develop
throughout the years will reflect in every bit of code.
Work neat I said. Merry Christmas. Or not, if you're not celebrating it.
No comments:
Post a Comment