C#.NET Thread多线程并发编程学习与常见面试题解析-2、同步与互斥简介+信号量+前后台线程

前言:上一期的最后我提到使用一种类似信号灯的机制,但那只是为了方便理解,严格意义来说上一期使用的是事件(event)。
上一期提到其实我们可以合并成一个函数,我在私下里已经用不同的方法验证过了,但因代码太丑也不好展开就不放上来了

一、互斥与同步

什么是互斥?
当多个线程访问同一个全局变量,或者同一个资源(比如打印机)的时候,需要进行线程间的互斥操作来保证访问的安全性。
什么是同步?
同步指的是线程之间需要按一定的顺序执行,例如我们上一期的那道题。

那么用什么方法来实现互斥和同步呢?
一般用一下四种:
1、临界区(CS:critical section):标识一段代码为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。
2、互斥量(Mutex):互斥量跟临界区很像,但是比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
3、信号量(Semaphores):为控制一个具有有限数量用户资源而设计。
4、事件(Event):用来通知线程有一些事件已发生,从而启动后继任务的开始。

可以看到我们上一期举例是信号灯(红绿灯)只是为了好懂,其实使用的是事件,解决的是同步的问题。那么我们这期来看一下另一个控制顺序的方法—信号量。
剩下的东西留到接下来几期慢慢讲。

二、信号量Semaphore

在C#中也有一个类System.Threading.Semaphore。功能是为了限制数量用户。
例如我们在魔兽争霸当中的金矿,允许进入一定数量的苦工(线程),苦工采完矿出来以后才能进入新的苦工(线程完成后新的线程才能开始工作)。
C#.NET Thread多线程并发编程学习与常见面试题解析-2、同步与互斥简介+信号量+前后台线程_第1张图片
在Semaphore有一个很重要的概念就是请求数(就是金矿里能有多少苦工啦)
Semaphore中的WaitOne()方法会判断是否还有请求数,如果还有的话会直接通过,请求数减一,如果无请求数了则会在该地方阻塞暂停,等到有请求数的时候才通过。
Release()方法则是释放一个请求数,当然也可以填参数一次性释放多个 ,但是如果释放的数量大于已有的数量会抛出异常。
(包括之前的我都没有讲全,也讲不全,毕竟有了概念后去MSDN查API还是很快的)

好,那下面就可以上代码了,模拟10个矿工采矿,但是金矿只能容纳3个人的情况。

using System;
using System.Threading;
namespace LeeCarry
{
    public class Test
    {
        private static Semaphore gold=new Semaphore(3,3);//第一个参数为目前可用请求数,第二个参数为最大请求数

        public static void Main(string[] args)
        {
            for(int i=1;i<=10;i++)
            {
                Thread t = new Thread(DoMining);
                t.Start(i);
            }
            
        }
        private static void DoMining(object obj)
        {
            int id=Convert.ToInt32(obj);
            for(int i=0;i<5;i++)
            {
                gold.WaitOne();//如果还有请求数就直接通过请求数减一,没有请求数就暂停在这等有请求时通过
                Console.WriteLine("我是{0}号矿工,现在开始采矿",id);
                Thread.Sleep(1000);
                Console.WriteLine("我是{0}号矿工,现在采矿完成",id);
                gold.Release();//释放请求数
            }
        }
    }
}

结果:
C#.NET Thread多线程并发编程学习与常见面试题解析-2、同步与互斥简介+信号量+前后台线程_第2张图片
当然因为截GIF比较麻烦,如果实际运行的话就会看到是3个开始3个完成成对出来的了。最开始的也不一定是123矿工,毕竟开线程间隔的时间太短,资源抢占情况不一定,但是可以保证的是只会同时有3个矿工在挖矿。

三、前台线程和后台线程

前台线程和后台线程有什么区别?
前台线程:
应用必须结束掉所有的前台线程才能结束程序,只要有一个前台线程没退出进程就不会自动退出,当然线程是依附在进程上的,所以你直接把进程KO掉了的话自然所有前台线程也会退出。
后台线程:
进程可以不考虑后台直接自动退出,进程自动退出后所有的后台线程也会自动销毁。
C#中怎么使用?
通过将 Thread.IsBackground 属性设置为 true,就可以将线程指定为后台线程

样例:在我们刚刚的采矿例子上加上一个每隔2秒自动采矿一次的后台线程。

using System;
using System.Threading;
namespace LeeCarry
{
    public class Test
    {
        private static Semaphore gold=new Semaphore(3,3);//第一个参数为目前可用请求数,第二个参数为最大请求数

        public static void Main(string[] args)
        {
            Thread backThread =new Thread(AutoMining);
            backThread.IsBackground=true;//标记为后台线程
            backThread.Start();
            for(int i=1;i<=5;i++)
            {
                Thread t = new Thread(DoMining);
                t.Start(i);
            }
            
        }
        private static void DoMining(object obj)
        {
            int id=Convert.ToInt32(obj);
            for(int i=0;i<5;i++)
            {
                gold.WaitOne();//如果还有请求数就直接通过请求数减一,没有请求数就暂停在这等有请求时通过
                Console.WriteLine("我是{0}号矿工,现在开始采矿",id);
                Thread.Sleep(1000);
                Console.WriteLine("我是{0}号矿工,现在采矿完成",id);
                gold.Release();//释放一个请求数
            }
        }

        private static void AutoMining()
        {
            while(true)
            {
                Console.WriteLine("我在默默挖矿。。。");
                Thread.Sleep(2000);
            }
        }




    }
}

这里我就不截图结果了,因为截静态图是体现不出自动结束的效果的(终端按ctrl+c也能结束进程),大家只需要知道每个两秒会自动输出一个 我在默默挖矿 就可以了,等前台苦工挖矿执行完以后整个进程会自动结束,当然如果你把backThread.IsBackground=true;注释掉就不会自动结束了。
如果你真的对结果比较感兴趣的话就自己复制跑一下吧,顺带我为了快点观察到自动结束把苦工(线程)数量改到了5。

那么本章的内容就是那么多,可以看到我是没对变量进行操作的,因为要涉及到一个资源抢占的问题,相对来说控制台输出还是比较算是线程安全的,具体内容可能下期讲,也可能之后几期讲。


引用
C++线程同步与互斥总结-凌冷
Visual C++线程同步技术剖析-C++乐园
Semaphore Class-MSDN
.NET面试题解析(07)-多线程编程与线程同步-/梦里花落知多少/

你可能感兴趣的:(C#,并发,异步,并行)