C# 线程基础(二)

文章详细介绍了.NET环境下的线程概念,包括前台线程和后台线程的差异,如何通过设置IsBackground属性切换线程类型。此外,还讨论了线程参数传递的方式,以及如何使用lock关键字和Monitor类实现线程同步,确保数据一致性,避免竞态条件。示例代码展示了线程的创建、执行和同步操作。
摘要由CSDN通过智能技术生成

目录

八、前台线程和后台线程

九、线程参数的传递

十、线程中的 lock 关键字

十一、Monitor类锁定

结束


八、前台线程和后台线程

默认情况下,显式创建的线程是前台线程,通过手动的设置 Thread 类的属性 IsBackground = true 来指示当前线程为一个后台线程。

前台线程和后台线程最主要的区别是,所有的前台线程执行完成后,后台线程会自动停止工作。

注意事项:

不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。

托管线程池中的线程都是后台线程,使用 new Thread 方式创建的线程默认都是前台线程。

代码:

using System;
using System.Threading;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var sample1 = new ThreadSample(10);
            var sample2 = new ThreadSample(20);

            var thread1 = new Thread(sample1.CountNum);
            thread1.Name = "thread1";

            var thread2 = new Thread(sample1.CountNum);
            thread2.Name = "thread2";
            thread2.IsBackground = true;

            thread1.Start();
            thread2.Start();

            Console.ReadKey();
        }
    }

    class ThreadSample
    {
        private readonly int iterations;

        public void CountNum()
        {
            for (int i = 0; i < iterations; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("线程名:{0}, i:{1}", Thread.CurrentThread.Name, i);
            }
        }

        public ThreadSample(int iterations)
        {
            this.iterations = iterations;
        }
    }
}

运行:

在上面的代码中可以看到定义如下

var sample1 = new ThreadSample(10);
var sample2 = new ThreadSample(20);

线程2的执行次数是20次,但是在打印中只执行了9次,线程2执行到第9次后,就停止运行了,因为这时候,前台线程已经执行完毕,后台线程也就跟随着一起结束了。

九、线程参数的传递

在线程的 Start 方法中,其实还有一个重载函数的,可以向线程执行的方法中传递一个参数,参考微软官方的代码:

[MethodImpl(MethodImplOptions.NoInlining)]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public void Start(object parameter)
{
    if (m_Delegate is ThreadStart)
    {
        throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadWrongThreadStart"));
    }

    m_ThreadStartArg = parameter;
    StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
    Start(ref stackMark);
}

可以看到,参数的类型为 object 类型,下面就看看 start 方法中的这个参数的用途吧

using System;
using System.Threading;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(Test);
            thread.Start(10);

            Console.ReadKey();
        }

        static void Test(object obj)
        {
            int len = (int)obj;
            for (int i = 0; i < len; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(500);
            }
        }
    }
}

运行:

将上面的方法换为委托,Lambda 表达式的写法,效果一样

using System;
using System.Threading;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread = new Thread(delegate (object obj)
            {
                int len = (int)obj;
                for (int i = 0; i < len; i++)
                {
                    Console.WriteLine(i);
                    Thread.Sleep(500);
                }
            });
            thread.Start(10);

            Console.ReadKey();
        }
    }
}

十、线程中的 lock 关键字

lock 往往用在多线程的开发中,如果有几个线程,同时调用一个方法,就会出现这个方法轮流执行的情况,这可能会导致部分数据混乱,如果想这几个线程按顺序执行,则需要用到 lock。

下面是 lock 的一些注意点:

1.lock 只对多线程有效,对单线程无效,单线程 lock 不会导致死锁。
2.不推荐使用 lock(this),因为在它外部也可以访问它。
3.不应该使用 lock(string(类型)),因为 string 在内存分配上是重用的,可能会导致冲突。
4.lock 中包含的代码最好不要太多,因为在这里是单线程运行的。
5. .net 提供了一些线程安全的集合类,使用这些集合不需要用到 lock。
6.在可以使用数据分拆的方法来使用多线程时,最好使用数据分拆而不使用 lock。
7.lock 的对象应该是 private static readonly object Object_Lock = new object();
 

下面的代码,其实在上面的案例中用到了多次

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test);
            thread1.Name = "thread1";
            thread1.Start();
            Thread thread2 = new Thread(Test);
            thread2.Name = "thread2";
            thread2.Start();

            Console.ReadKey();
        }

        static int count = 0;

        static void Test()
        {
            while (count < 20)
            {
                count++;
                Console.WriteLine("当前线程:{0},次数:{1}", Thread.CurrentThread.Name, count);
                Thread.Sleep(50);
            }
        }
    }
}

运行:

从结果可以看到,方法是由这两个线程轮流执行的,比较混乱,如果线程执行的方法中有全局变量,那么有可能导致计算结果是不对的,下面,我们将代码更改一下

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test);
            thread1.Name = "thread1";
            thread1.Start();
            Thread thread2 = new Thread(Test);
            thread2.Name = "thread2";
            thread2.Start();

            Console.ReadKey();
        }

        private static readonly object obj = new object();

        private static int thread1Count = 0;
        private static int thread2Count = 0;

        static void Test()
        {
            lock (obj)
            {
                while (true)
                {
                    if (Thread.CurrentThread.Name == "thread1")
                        thread1Count++;
                    if (Thread.CurrentThread.Name == "thread2")
                        thread2Count++;

                    Console.WriteLine("线程:{0} thread1Count:{1}, thread2Count:{2}", Thread.CurrentThread.Name, thread1Count, thread2Count);

                    Thread.Sleep(50);

                    if (thread1Count > 5)
                    {
                        thread1Count = 0;
                        break;
                    }
                    if (thread2Count > 5)
                    {
                        thread2Count = 0;
                        break;
                    }
                }
            }
        }
    }
}

thread1Count = 0 是防止线程1执行完后,线程2执行时,依然满足条件,而直接退出了循环,所以,我将 thread1Count 设置为 0 了。

运行

从打印的结果来看,现在不再是两个线程轮流着执行了,这就是我们想要的效果了。

十一、Monitor类锁定

在上节中,我们使用了 lock 来锁定多线程的执行,实际上 lock 关键字是 Monitor 类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码所示:

object lockObject = new object();
bool acquiredLock = false;
try
{
    Monitor.Enter(lockObject, ref acquiredLock);
}
finally
{
    if (acquiredLock)
    {
        Monitor.Exit(lockObject);
    }
}

Monitor的常用属性和方法:

  • Enter(Object) 在指定对象上获取排他锁。
  • Exit(Object) 释放指定对象上的排他锁。
  • Pulse 通知等待队列中的线程锁定对象状态的更改。
  • PulseAll 通知所有的等待线程对象状态的更改。
  • TryEnter(Object) 试图获取指定对象的排他锁。
  • TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
  • Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。

案例:

using System;
using System.Threading;

namespace Test5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod);
            threadA.Name = "线程A";
            Thread threadB = new Thread(ThreadMethod);
            threadB.Name = "线程B";
            threadA.Start();
            threadB.Start();

            Console.ReadKey();
        }

        private static readonly object obj = new object();
        public static void ThreadMethod()
        {
            //锁定对象
            Monitor.Enter(obj);
            try
            {
                for (int i = 1; i <= 10; i++)
                {
                    Console.Write("{0}:{1}  ", Thread.CurrentThread.Name, i);
                }
                Console.WriteLine();
            }
            finally
            {
                //释放对象
                Monitor.Exit(obj); 
            }
        }
    }
}

运行:

结束

如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言,谢谢!

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊思宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值