humblelistener
humblelistener

Reputation: 1456

Thread safety in parameters passed to a static method

Assuming a static method like below is called from ASP.NET page, can a different thread(b) overwrite the value of s1 after the first line is executed by thread(a)?

If so, can assigning parameters to local variables before manipulation solve this?

public static string TestMethod(string s1, string s2, string s3)
{
   s1 = s2 + s3;
   ....
   ...
   return s1;
}

Is there are a simple way to recreate such thread safety related issues?

Thanks.

Upvotes: 0

Views: 1687

Answers (4)

Suman Kr. Nath
Suman Kr. Nath

Reputation: 135

I had same confusion too and here is my test code. Just sharing it ...

public partial class _default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ThreadTest b = new ThreadTest();
        Thread t = new Thread(new ParameterizedThreadStart(ThreadTest.sum));
        Thread t1 = new Thread(new ParameterizedThreadStart(ThreadTest.sum));
        t.Start(10);
        t1.Start(12);

    }
}

class ThreadTest
{

    public static void sum(object XX)
    {
        int x = (int)XX;
        for (int i = 0; i < x; i++)
        {
            System.Diagnostics.Debug.WriteLine("max : " + x + "  ---  " + i.ToString());
        }
    }
}

... Now if you run this you will see that int x is safe. so local non static variables are safe for a process and can not crippled by multiple thread

Upvotes: 1

sisve
sisve

Reputation: 19781

Yes, under some condition, as seen by the example code.

public static class ConsoleApp {
    public static void Main() {
        Console.WriteLine("Write something.");
        var str = Console.ReadLine();
        if (String.IsNullOrEmpty(str))
            return;

        new Thread(() => TestMethod(null, str, "")).Start();

        // Allow TestMethod to execute.
        Thread.Sleep(100);

        unsafe {
            // Grab pointer to our string.
            var gcHandle = GCHandle.Alloc(str, GCHandleType.Pinned);
            var strPtr = (char*)gcHandle.AddrOfPinnedObject().ToPointer();

            // Change it, one character at a time, wait a little more than
            // TestMethod for dramatic effect.
            for (int i = 0; i < str.Length; ++i) {
                strPtr[i] = 'x';
                Thread.Sleep(1100);
            }
        }

        // Tell TestMethod to quit.
        _done = true;
        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    private static Boolean _done;
    public static void TestMethod(String x, String y, String z) {
        x = y + z;

        while (!_done) {
            Console.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), x);
            Thread.Sleep(1000);
        }
    }
}

Requirements (afaik)

  1. Unsafe context to use pointers.
  2. Use String.Concat(String str0, String str1) which is optimized for cases where str0 == String.Empty or str1 == String.Empty, which returns the non-empty string. Concatenating three or more strings would create a new string which blocks this.

Here's a fixed version of your modified one.

public static class ConsoleApp {
    private static Int32 _counter = 10;

    public static void Main() {
        for (var i = 0; i < 10; i++) {
            var str = GetString();
            Console.WriteLine("Input: {0} - {1}", DateTime.Now.ToLongTimeString(), str);
            new Thread(() => TestMethod(str)).Start();

            unsafe {
                var gcHandle = GCHandle.Alloc(str, GCHandleType.Pinned);
                var strPtr = (char*)gcHandle.AddrOfPinnedObject().ToPointer();
                strPtr[0] = 'A';
                strPtr[1] = 'B';
                strPtr[2] = 'C';
                strPtr[3] = 'D';
                strPtr[4] = 'E';
            }
        }

        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    private static String GetString() {
        var builder = new StringBuilder();

        for (var i = _counter; i < _counter + 10; i++)
            builder.Append(i.ToString());

        _counter = _counter + 10;
        return builder.ToString();
    }

    public static void TestMethod(Object y) {
        Thread.Sleep(2000);
        Console.WriteLine("Output: {0} {1}", DateTime.Now.ToLongTimeString(), y);
    }
}

This still works because Object.ToString() is overriden in String to return this, thus returning the exact same reference.

Upvotes: 0

humblelistener
humblelistener

Reputation: 1456

Thanks Simon, here is the my evaluation on this. In the following code, i spawn threads simply using Thread.Start and the output becomes inconsistent.

This is proving that string passed on to a method can be modified.

If otherwise please explain!

public static class ConsoleApp{

    [ThreadStatic]
    private static int counter = 10;

    public static void Main()
    {            
        string str;

        object obj = new object();
        // Change it, one character at a time, wait a little more than
        // TestMethod for dramatic effect.            
        for (int i = 0; i < 10; i++)
        {
            lock (obj)
            {
                str = GetString();
                Console.WriteLine(DateTime.Now.ToLongTimeString());
                //ThreadPool.QueueUserWorkItem(TestMethod, str);
                new Thread(() => TestMethod(str)).Start();
            }
        }

        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    private static string GetString()
    {
        object obj = new object();
        lock (obj)
        {
            StringBuilder sb = new StringBuilder();
            int temp = 0;
            for (int i = counter; i < counter + 10; i++)
            {
                sb.Append(i.ToString());
                temp = i;
            }
            counter = temp;            
            return sb.ToString();
        }
    }

    public static void TestMethod(object y)
    {   
        Thread.Sleep(2000);
        Console.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), y.ToString());
    }
}

Thanks.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499790

No, the parameters are local variables - they're independent of any other threads. As strings are also immutable, you're safe. If these were mutable - e.g. a parameter of StringBuilder s1 - then although the value of s1 (a reference) couldn't be changed, the object that the parameter referred to could change its contents.

ref and out parameters could potentially have issues, as they can alias variables which are shared between threads.

Upvotes: 4

Related Questions