Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread-Safe Code Becomes Too Clumsy? [closed]

I never actually wrote a multithreading application.

In the few times I did write one, it seems to me thread-safety gets too clumsy too quickly.

The internet is full of tutorials about thread-safety general technique, but there's not much I found about real world programing issues.

for example, taking this simple code, (does nothing usable)

    StringBuilder sb = new StringBuilder();
    void Something()
    {
        sb.AppendLine("Numbers!");
        int oldLength = sb.Length;
        int i = 0;
        while (sb.Length < 500 + oldLength)
        {
            sb.Append((++i).ToString());
            //something slow
            Thread.Sleep(1000);
            if (i % 2 == 0)
            {
                sb.Append("!");
            }
            sb.AppendLine();
        }
    }

Now assuming I want to run this method from multiple threads, all writing to the same string builder.

I want them to write to the same target together, so it's possible that there will be one line from one thread, and right after it another line from another thread, and then the next line back from the first thread.

for the sake of the conversation, let's assume it's ok even if there a line from one thread in the middle of another line

And here's the code:

    StringBuilder sb = new StringBuilder();
    object sbLocker = new object();
    void SomethingSafe()
    {
        int oldLength;
        int length;

        lock (sbLocker)
        {
            sb.AppendLine("Numbers!");
            oldLength = sb.Length;
            length = oldLength;
        }


        int i = 0;

        while (length < 500 + oldLength)
        {
            lock (sbLocker)
                sb.Append((++i).ToString());

            //something slow
            Thread.Sleep(1000);
            if (i % 2 == 0)
            {
                lock (sbLocker)
                    sb.Append("(EVEN)");
            }

            lock (sbLocker)
            {
                sb.AppendLine();
                length = sb.Length;
            }
        }
    }

So exhausting and unreadable...

Isn't there any way to tell the compiler to just simply lock sbLocker every time there's any access to sb?

Why my code need to be so clumsy for such a simple rule? There's no much thinking into this specific, yet very usable, technique. Can it be done anyway easier?

We can't even inherit StringBuilder since it's sealed.

Of Course, one can go ahead and wrap the whole class:

public class SafeStringBuilder
{
    private StringBuilder sb = new StringBuilder();
    object locker = new object();
    public void Append(string s)
    {
        lock (locker)
        {
            sb.Append(s);
        }
    }

    //................
}

but this is just crazy... as there are so many different classes we are using.

Any idea how to make thread safe practice in this sense?

I know that for creating the exact same result there might be a simpler solution... but this is just an example. I'm pretty sure I've encountered similar problems without any readable solution possible.

like image 931
Letterman Avatar asked May 05 '26 13:05

Letterman


2 Answers

You are correct in that writing threadsafe code is potentially infinitely more complicated than writing single threaded code. It is something that probably should be avoided wherever it isn't necessary.

You are incorrect in assuming that it can't be written in a readable fashion. Unfortunately, it is really hard to make this into an answer, and the best I can do is offer some guidelines:

  1. Find logical places for divisions in threads. Something like building a common string doesn't really make sense as it won't speed up what you're doing. In order for multi-threading to make sense, there has to be some part that can be done independently (or mostly independently) of other pieces of the program. Good examples include matrix multiplication and a server responding to client requests. Chances are that if you are multi-threading something that isn't a good fit for multi-threading, it will be exceptionally difficult to write elegant code.
  2. Wherever data sharing is necessary, attempt to follow the model: lock, access/alter, unlock.
  3. Follow a hierarchy for locking order.
  4. Avoid directly sharing data through duplication, message passing, etc. wherever possible.
  5. Hide the locking from as much of the rest of the code as much as possible. Give them functions that handle the shared data wherever possible.

Unfortunately, writing elegant multi-threaded code is more something that you perfect over years of effort than something that can be taught in a stack overflow question, but hopefully this answer sheds some light.

like image 158
dbeer Avatar answered May 07 '26 19:05

dbeer


Some framework classes provide you with the thread-safe wrappers that you can use. For example, you can create a StringWriter over the StringBuilder, and then use TextWriter.Synchronized to get a thread-safe wrapper that can be accessed simultaneously from several threads.

var sb = new StringBuilder();
var tw = new StringWriter(sb);

var threadSafeWriter = TextWriter.Synchronized(tw);

threadSafeWriter.Write("Hello");
threadSafeWriter.WriteLine(" world");

And, there also thread-safe concurrent collections that can be handy.

If you really want "a compiler to lock object every time it is accessed" without writing custom wrapper for each type, you can use some library, like Castle.Proxy, to generate wrappers at runtime. However, in non-trivial scenarios, when multiple object accesses should be performed atomically, this would not produce the results you need.

like image 27
alex Avatar answered May 07 '26 19:05

alex



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!