C# 线程同步方法

1、lock

锁定的是一个引用类型,值类型不能被锁定,但应该避免锁定一个string,因为string的存储方式是不确定的。

主要注意的就是被锁的这个对象。

class Program
    {
        private static readonly object objLock = new object();
        static int te = 10;
        static int num = 0;
        static void Main(string[] args)
        {
            for (int i = 0; i < 10000; i++)
            {
                Task.Run(Method);
            }

             Thread.Sleep(100);
            Console.WriteLine(num.ToString());
            Console.ReadLine();
        }
        public static void Method()
        {
            lock (objLock)
            {
                num++;
            }
            // num++;
        }
    }

 

2、InterLocked

如果一个变量被多个线程修改,读取。可以用InterLocked

计算机上不能保证对一个数据的增删是原子性的,因为对数据的操作也是分步骤的:

  1.将实例变量中的值加载到寄存器中。

  2.增加或减少该值。

  3.在实例变量中存储该值。

InterLocked为多线程共享的变量提供原子操作。

InterLocked提供了需要原子操作的方法:

    class Program
    {
        public static int value1 = 0; 
        public static int value2 = 0; 
        static void Main(string[] args)
        {
            TestIncrement();
            Thread.Sleep(2000);
            Console.WriteLine(value1+"\n"+value2);
            Console.ReadLine();
        }
        public static void TestIncrement()
        {
            for(int i = 0; i < 5; i++){
                Task.Run(IncrementValue);
            }
        }
        public static void IncrementValue()
        {
            for(int i = 0; i < 1000000; i++)
            {
                value1++;
                Interlocked.Increment(ref value2);
            }
        }
    }

 

3、Moniter

排他锁

lock其实就是Moniter的语法糖,二者其实就是一样的,Moniter可以更灵活就是了。可以使用TryEnter设置最大等待时间

	class Program
	{
		private static readonly object lock1 = new object();
		private static readonly object lock2 = new object();
		static void Main(string[] args)
		{

			Task.Run(LockTooMuch);

			lock (lock2)
			{
				Thread.Sleep(1000);	//这里拿到了lock2,而LockTooMuch拿到了lock1
				Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false after a specified timeout is elapsed");
				if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))	//拿不到资源,等5s后就进入else语句了
				{
					Console.WriteLine("Acquired a protected resource succesfully");
				}
				else
				{
					Console.WriteLine("Timeout acquiring a resource!");
				}
			}

			Task.Run(LockTooMuch);

			Console.WriteLine("----------------------------------");
			lock (lock2)	//这里会直接死锁
			{
				Console.WriteLine("This will be a deadlock!");
				Thread.Sleep(1000);
				lock (lock1)
				{
					Console.WriteLine("Acquired a protected resource succesfully");
				}
			}
		}

		static void LockTooMuch()
		{
			lock (lock1)
			{
				Thread.Sleep(1000);
				lock (lock2) { };
			}
		}
	}

 

4、WaitHandle

参考博客:点这里

这个类是一个抽象类,是用来封装等待对共享资源进行独占访问的操作系统特定的对象。

 

5、AutoResetEvent

AutoResetEvent表示线程同步事件在一个等待线程释放后收到信号时自动重置。WaitOne()看信号,有信号不阻塞,没信号就阻塞,Set()发出信号,这里WaitOne()操作之后会自动重置一次信号,即自动调用一次Reset()方法。

 

class Program
    {
        static int num = 0;
        private static AutoResetEvent ate1 = new AutoResetEvent(false);
        private static AutoResetEvent ate2 = new AutoResetEvent(true);  //设为True表示一开始没有设置Reset(),所以第一个WaitOne()没有效果

        static void Main(string[] args)
        {
            Task.Run(Method1);
            Task.Run(Method2);

            Console.ReadLine();
        }
        public static void Method1()
        {
            while (num < 100)
            {
                ate1.WaitOne();//这里自动的调用了ate1.Reset()方法
                Console.WriteLine("奇数:"+num++.ToString());
                ate2.Set(); 
            }
        }
        public static void Method2()
        {
            while (num < 100)
            {
                ate2.WaitOne();
                Console.WriteLine("偶数:" + num++.ToString());
                ate1.Set();
            }
        }
    }

WaitOne还有一些重载,加一个整数参数表示最大等待时长上限,-1就表示无限等了,返回值为bool型,等到了就是True,没等到就是False。

 

6、ManualResetEvent

和AutoResetEvent的区别就和它们的名称一样,一个自动Reset(),一个手动Reset()。其他地方完全一样

    class Program
    {
        static int num = 0;
        private static ManualResetEvent ate1 = new ManualResetEvent(false);
        private static ManualResetEvent ate2 = new ManualResetEvent(true);  //设为True表示一开始没有设置Reset(),所以第一个WaitOne()没有效果

        static void Main(string[] args)
        {
            Task.Run(Method1);
            Task.Run(Method2);

            Console.ReadLine();
        }
        public static void Method1()
        {
            while (num < 100)
            {
                ate1.WaitOne();
                Console.WriteLine("奇数:" + num++.ToString());
                ate1.Reset();
                ate2.Set();
            }
        }
        public static void Method2()
        {
            while (num < 100)
            {
                ate2.WaitOne();
                Console.WriteLine("偶数:" + num++.ToString());
                ate2.Reset();
                ate1.Set();
            }
        }
    }

 

7、Semaphore

其实.NET中的信号量(Semaphore)是操作系统维持的一个整数。当整数位0时。其他线程无法进入。当整数大于0时,线程可以进入。每当一个线程进入,整数-1,线程退出后整数+1。整数不能超过信号量的最大请求数。信号量在初始化的时候可以指定这个整数的初始值。

WaitOne()方法表示当前线程占用信号量,Release() 方法表示当前线程释放信号量

    class Program
    {
        static void Main(string[] args)
        {
            Foo foo = new Foo();

            Console.ReadLine();
        }
    }

    public class Foo
    {
        Semaphore semaphore = new Semaphore(0, 3);//允许最大3个线程占用信号量 超过后就会在WaitOne那阻塞

        public Foo()
        {
            for (int i = 0; i < 10; i++)
            {
                int j = i;
                Task.Run(() => DoSomething(j));
            }
            //semaphore.Release(3);
            //Console.WriteLine(semaphore.Release()); 
            //Console.WriteLine(semaphore.Release()); 
            //Console.WriteLine(semaphore.Release()); 
            //Console.WriteLine(semaphore.Release()); 

        }

        private void DoSomething(int i)
        {
            semaphore.WaitOne();//当前线程占用信号量  如果信号量被占满 则阻塞
            Console.WriteLine($"{i}进入信号量");

            Thread.Sleep(1000);

            Console.WriteLine($"{i}释放信号量  信号量线程数量:{semaphore.Release()}");//当前线程释放信号量
        }
    }

 

8、SemaphoreSlim

SimaphoreSlim是轻量级的,操作和Semapore是一样的

本质区别是 Semaphore 是基于系统提供的同步原语实现的同步等待,而 SemaphoreSlim 是通过自旋(基于 SpinWait) 实现的同步等待。

Slim 版本在大多数情况下不会出现用户态到系统态的转换,而 Semaphore 则非常可能发生这种情况。但由于 SpinWait 的特点,Slim 版本更加适用于等待时间较短的场景。因为如果出现了长时间的等待(长过一次工作状态的切换),SpinWait 会放弃继续空循环的操作,将执行机会让给其他的线程,这样又会导致上下文的切换。

    class Program
    {
        static void Main(string[] args)
        {
            Foo foo = new Foo();

            Console.ReadLine();
        }
    }

    public class Foo
    {
        Semaphore semaphore = new Semaphore(0, 3);//允许最大3个线程占用信号量 超过后就会在WaitOne那阻塞
        SemaphoreSlim semaphoreSlim = new SemaphoreSlim(5);

        public Foo()
        {
            for (int i = 0; i < 10; i++)
            {
                int j = i;
                Task.Run(() => DoSomething(j));
            }
            Thread.Sleep(10000);
            //semaphore.Release();
            //semaphore.Release(3);
            Console.WriteLine(semaphoreSlim.Release()); 
            //Console.WriteLine(semaphore.Release()); 
            //Console.WriteLine(semaphore.Release()); 
            //Console.WriteLine(semaphore.Release()); 

        }

        private void DoSomething(int i)
        {
           // semaphore.WaitOne();//当前线程占用信号量  如果信号量被占满 则阻塞
            semaphoreSlim.Wait();
            Console.WriteLine($"{i}进入信号量");

            Thread.Sleep(1000);

           // Console.WriteLine($"{i}释放信号量  信号量线程数量:{semaphore.Release()}");//当前线程释放信号量
            Console.WriteLine($"{i}释放信号量  信号量线程数量:{semaphoreSlim.Release()}");//当前线程释放信号量
        }
    }

9、Mutex

互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它,互斥锁可适用于一个共享资源每次只能被一个线程访问的情况,如果线程获取互斥体,则需要获取该互斥体的第二个线程将挂起,直到第一个线程释放该互斥体。

在Mutex类中,WaitOne()方法用于等待资源被释放,ReleaseMutex()方法用于释放资源。WaitOne()方法在等待ReleaseMutex()方法执行后才会结束。

    class Program
    {
        static Mutex mutex = new Mutex();//互斥锁      
        static bool hasPay = false;
        static void Pay(int num)
        {
            if (mutex.WaitOne())
            {
                try
                {
                    if (!hasPay)
                    {
                        Console.WriteLine($"编号{num}:已经买单了");
                        hasPay = true;
                    }
                    else
                        Console.WriteLine($"编号{num}:买单失败");
                }
                finally
                {
                    mutex.ReleaseMutex();
                    Console.WriteLine("mutex is Release");
                }
            }
        }
        static void Main(string[] args)
        {
            for (int i = 1; i < 10; i++)
            {
                int j = i;
                Task.Factory.StartNew(()=>Pay(j));
            }
            Console.ReadLine();
        }
    }

通过设置Mutex的名称可以做到跨进程,让整个操作系统实现线程同步操作。默认的初始所有没有方法确认,所以一般设置为false

    class Program
    {
        static string name = "Xunzf";
        static void Main(string[] args)
        {
            //for (int i = 1; i < 10; i++)
            //{
            //    int j = i;
            //    Task.Factory.StartNew(()=>Pay(j));
            //}
            //Console.ReadLine();

            //一般建议把第一个参数设为false ,因为没有特定机制来确定这个名称的互斥量是否被创建了
            Mutex mu = new Mutex(false, name);
            if (mu.WaitOne())
            {
                Console.WriteLine("程序1开始运行");
            }
            //mu.ReleaseMutex();

            //验证createdNew参数作用
            //bool createdNew;
            //Mutex mu = new Mutex(false, name, out createdNew);
            //if (createdNew)
            //{
            //    Console.WriteLine("创建成功!");
            //}
            //else
            //{
            //    Console.WriteLine("创建失败!");
            //}

            Console.WriteLine("输入回车结束程序");
            Console.ReadLine();
            //mu.ReleaseMutex();
        }
    }
验证端
     class Program
    {
        static string name = "Xunzf";

        static void Main(string[] args)
        {
            Mutex mu = new Mutex(false, name);
            if (mu.WaitOne())
            {
                Console.WriteLine("程序2开始运行");
            }

            Console.WriteLine("输入回车结束程序");
            Console.ReadLine();
        }
    }

 

posted @ 2022-08-16 21:01  xunzf  阅读(284)  评论(0编辑  收藏  举报