. NET Learning Difficulties Discussion Series 17-use of thread-local variables

Source: Internet
Author: User

Most of the articles on C # multithreading are talking about thread start-up or multithreaded synchronization issues. Multithreading is accessing the same variable in different threads (typically a variable outside the thread's work function), and it is well known that without the use of thread synchronization, the presence of the state causes some threads to have dirty reads or overwrite values that other threads have written (various confusions). Another scenario is that the variables that we want the thread to access are owned by the thread itself, which is called a thread-local variable.
Below we will gradually extend the simplest sample code to demonstrate the difference between the variable access and the thread-local variables and their respective solutions as described above.

The example shown here is simple. The variable being accessed is a "number of apples in the bag", and the working function is "put an apple in the bag".

public class Bag{    public int AppleNum { get; set; }}public class Test{    public void TryTwoThread()    {        var b = new Bag();        Action localAct = () =>        {            for (int i = 0; i < 10; i++)            {                    ++b.AppleNum;                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");                Thread.Sleep(100);            }        };        Parallel.Invoke(localAct, localAct);    }}// Program.csvar tester = new Test();tester.TryTwoThread();

As the code shows, this is a classic code for concurrent access errors for multithreaded variables. Because there is no code for concurrent access control, the execution result is indeterminate. The result we expect is that there are 20 apples in the bag, which is difficult to achieve in practice.

Because the execution results are not deterministic, the above shows only one of the randomly occurring cases.

The way to solve this problem is to use concurrency control, the easiest way is to add a lock to the shared variable access.

public class Test{    private object _locker = new object();    public void TryTwoThread()    {        var b = new Bag();        Action localAct = () =>        {            for (int i = 0; i < 10; i++)            {                    lock(_locker)                {                    ++b.AppleNum;                    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");                }                Thread.Sleep(100);            }        };        Parallel.Invoke(localAct, localAct);    }}

The result will be guaranteed, and eventually there'll be 20 apples in the bag. Of course there are other concurrency control methods, but that is not the focus of this article to ignore not to say.

In some scenarios we have another requirement, and we are concerned with how many apples each thread puts into the bag. At this point we need to make the bag object relevant to the thread (there are multiple bags, each of which is thread-owned). This will require the content to be covered in this article-thread-local variables.

In the case where thread-local variables are not used, an easy way to accomplish this is to put the variables inside the function as internal variables.

public class Test{    public void TryTwoThread()    {        Action localAct = () =>        {            var b = new Bag(); //把变量访问工作函数当中            for (int i = 0; i < 10; i++)            {                ++b.AppleNum;                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {b.AppleNum}");                Thread.Sleep(100);            }        };        Parallel.Invoke(localAct, localAct);    }}

You can see the results as we wish.

If our work function is independent of a class, and the variable to be accessed concurrently is a member of this class, this method does not apply.
The previous example type of action is replaced by the following work class:

public class Worker{    private Bag _bag = new Bag();    public void PutTenApple()    {                    for (int i = 0; i < 10; i++)        {            PutApple();            Show();            Thread.Sleep(100);        }    }    private void PutApple()    {        ++_bag.AppleNum;    }    private void Show()    {        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {_bag.AppleNum}");    }}

The test method should read:

public void TryTwoThread(){    var worker = new Worker();    Parallel.Invoke(worker.PutTenApple, worker.PutTenApple);}

Note that the worker class above is also an example that does not satisfy each of our threads to independently manipulate their associated variable requirements. And because there is no concurrency control, the execution result of the program is not controllable.

We can also _bag declare variables in PutTenApple the same way as thread-local variables, but PutApple it is necessary to Show pass arguments when calling and methods.

Here are a few ways to implement thread-local variables.

Thread-related static fields

The first method for thread-related static fields is to use ThreadStatic Attribute. This is also a better way for Microsoft to recommend performance.
This is done by declaring the member variable static and marking [ThreadStatic] it. We make the following modifications based on the previous code:

[ThreadStatic] private static Bag _bag = new Bag();

Note that this implementation is problematic. This is detailed below.

If you also have resharper this cosmic plugin installed on your VS, you will see a hint like this in the code that initializes the static variable:

On this tip, ReSharper official website also has an explanation.

Simply put, the above initializer will only be called once, resulting in the result that only the first thread executing this method will get the value of the member correctly, and then _bag when the process is accessed again _bag , it will find that it is _bag still uninitialized-null.

The solution I chose for this problem was to initialize the variable in the working method _bag .

public class Worker{    [ThreadStatic] private static Bag _bag;    public void PutTenApple()    {        _bag = new Bag(); //调用前初始化        for (int i = 0; i < 10; i++)        {            PutApple();            Show();            Thread.Sleep(100);        }    }    private void PutApple()    {        ++_bag.AppleNum;    }    private void Show()    {        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {_bag.AppleNum}");    }}

The ReSharper Web site provides a way to wrap the static field with a property and to change access to the static field to the static property.

public class Worker{    [ThreadStatic] private static Bag _bag;    public static Bag Bag => _bag ?? (_bag = new Bag());    public void PutTenApple()    {        for (int i = 0; i < 10; i++)        {            PutApple();            Show();            Thread.Sleep(100);        }    }    private void PutApple()    {        ++Bag.AppleNum;    }    private void Show()    {        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {Bag.AppleNum}");    }}

For thread-local variables, if accessed out-of-line, it is not affected by threading operations.

public void TryTwoThread(){    var worker = new Worker();    Parallel.Invoke(worker.PutTenApple, worker.PutTenApple);    Console.WriteLine($"Main Thread : {Thread.CurrentThread.ManagedThreadId} - {Worker.Bag.AppleNum}");}

Access in the main thread:

Data slots

Another method of equivalence is to use LocalDataStoreSlot , but performance is inferior to the ThreadStatic method described above.

public class Worker{    private LocalDataStoreSlot _localSlot = Thread.AllocateDataSlot();    public void PutTenApple()    {        Thread.SetData(_localSlot, new Bag());        for (int i = 0; i < 10; i++)        {            PutApple();            Show();            Thread.Sleep(100);        }    }    private void PutApple()    {        var bag = Thread.GetData(_localSlot) as Bag;        ++bag.AppleNum;    }    private void Show()    {        var bag = Thread.GetData(_localSlot) as Bag;        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} - {bag.AppleNum}");    }}

The thread-related data is stored in the LocalDataStoreSlot object and Thread accessed through GetData and through SetData .

A data slot also has a named allocation method:

private LocalDataStoreSlot _localSlot = Thread.AllocateNamedDataSlot("Apple");public void PutTenApple(){    _localSlot = Thread.GetNamedDataSlot("Apple");//演示用    Thread.SetData(_localSlot, new Bag());    for (int i = 0; i < 10; i++)    {        PutApple();        Show();        Thread.Sleep(100);    }}

In the case of multiple components, it is useful to differentiate a data slot with a different name. But if you accidentally give the same name to different components, it can result in data contamination.
The performance of data slots is low, Microsoft is not recommended, and is not a strong type, it is not easy to use.

. NET 4-threadlocal

A new generic local variable storage mechanism has been added after the. NET Framework 4. ThreadLocal<T> The following example is also modified on the basis of the previous example. In contrast to the previous code, it is well understood that the ThreadLocal<T> ThreadLocal<T> constructor receives a lambda for deferred initialization of thread-local variables, and the value of the local variable can be accessed through the Value property. IsValueCreated can tell if a local variable has been created.

  public class worker{private threadlocal<bag> _baglocal = new Threadlocal<bag> (() = new Bag ()    , true);    Public threadlocal<bag> baglocal = _baglocal; public void Puttenapple () {if (_baglocal.isvaluecreated)//After the first access, the thread local variable is created {Console.writ        Eline ($ "{Thread.CurrentThread.ManagedThreadId}-initialized");            } for (int i = 0; i < i++) {putapple ();            Show ();        Thread.Sleep (100); } if (_baglocal.isvaluecreated) {Console.WriteLine ($ "{Thread.CurrentThread.ManagedThreadId}-First        Initial "); }} private void Putapple () {var bag = _baglocal.value;//Access ++bag through the Value property.    Applenum; } private void Show () {var bag = _baglocal.value;//through the Value property access Console.WriteLine ($ "{THREAD.CURRENTTH Read. Managedthreadid}-{bag.    Applenum} "); }}

In addition, if the ThreadLocal<T> trackallvalues is set to true at initialization time, the ThreadLocal<T> value stored in the thread local variable can be accessed outside the thread being used. As in the test code:

public void TryTwoThread(){    var worker = new Worker();    Parallel.Invoke(worker.PutTenApple, worker.PutTenApple);    // 可以使用Values在线程外访问所有线程本地变量(需要ThreadLocal初始化时将trackAllValues设为true)    foreach (var tval in worker.BagLocal.Values)    {        Console.WriteLine(tval.AppleNum);    }}

The thread local variable is written here. You are welcome to add.

. NET Learning Difficulties Discussion Series 17-use of thread-local variables

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.