在.Net框架中 C# 实现多线程的同步方法详解

C# 实现多线程启动停止暂停继续 多线程编程是提升应用程序性能和响应能力的关键技术之一。C# 提供了强大的多线程支持,能够轻松创建并发任务,优化资源利用,并改善用户体验。然而,实现多线程的同时,如何安全有效地管理这些线程(如启动、停止、暂停和继续)是一个重要的问题。大部分初学者在学习C#上位机编程时,多线程是一个很难逾越的鸿沟,不合理地使用多线程,会导致经常出现各种奇怪的问题,这也是很多初学者不敢使用多线程的原因。但是在实际开发中,多线程是一个不可避免的技术栈,基本上每个项目都会使用到,因此学好多线程技术,很重要。 阅读详情

本文主要描述在C#中线程同步的方法。线程的基本概念网上资料也很多就不再赘述了。直接接入主题,在多线程开发的应用中,线程同步是不可避免的。在.Net框架中,实现线程同步主要通过以下的几种方式来实现,在MSDN的线程指南中已经讲了几种,本文结合作者实际中用到的方式一起说明一下。

  1. 维护自由锁(InterLocked)实现同步
  2. 监视器(Monitor)和互斥锁(lock)
  3. 读写锁(ReadWriteLock)
  4. 系统内核对象
    1. 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)
    2. 线程池

除了以上的这些对象之外实现线程同步的还可以使用Thread.Join方法。这种方法比较简单,当你在第一个线程运行时想等待第二个线程执行结果,那么你可以让第二个线程Join进来就可以了。

自由锁(InterLocked)

对一个32位的整型数进行递增和递减操作来实现锁,有人会问为什么不用++或--来操作。因为在多线程中对锁进行操作必须是原子的,而++和--不具备这个能力。InterLocked类还提供了两个另外的函数Exchange, CompareExchange用于实现交换和比较交换。Exchange操作会将新值设置到变量中并返回变量的原来值: int oVal = InterLocked.Exchange(ref val, 1)。

监视器(Monitor)

在MSDN中对Monitor的描述是: Monitor 类通过向单个线程授予对象锁来控制对对象的访问。

Monitor类是一个静态类因此你不能通过实例化来得到类的对象。Monitor的成员可以查看MSDN,基本上Monitor的效果和lock是一样的,通过加锁操作Enter设置临界区,完成操作后使用Exit操作来释放对象锁。不过相对来说Monitor的功能更强,Moniter可以进行测试锁的状态,因此你可以控制对临界区的访问选择,等待or离开, 而且Monitor还可以在释放锁之前通知指定的对象,更重要的是使用Monitor可以跨越方法来操作。Monitor提供的方法很少就只有获取锁的方法Enter, TryEnter;释放锁的方法Wait, Exit;还有消息通知方法Pulse, PulseAll。经典的Monitor操作是这样的:

        // 通监视器来创建临界区 
        static public void DelUser(string name)
        {
            try
            {
                // 等待线程进入 
                Monitor.Enter(Names);
                Names.Remove(name);
                Console.WriteLine("Del: {0}", Names.Count);
                Monitor.Pulse(Names);
            }
            finally
            {
                // 释放对象锁 
                Monitor.Exit(Names);
            }
        } 
    }

其中Names是一个List, 这里有一个小技巧,如果你想声明整个方法为线程同步可以使用方法属性:

        // 通过属性设置整个方法为临界区 
        [MethodImpl(MethodImplOptions.Synchronized)] 
        static public void AddUser(string name) 
        { 
            Names.Add(name); 
            Console.WriteLine("Add: {0}",Names.Count); 
        }

对于Monitor的使用有一个方法是比较诡异的,那就是Wait方法。在MSDN中对Wait的描述是: 释放对象上的锁以便允许其他线程锁定和访问该对象。

这里提到的是先释放锁,那么显然我们需要先得到锁,否则调用Wait会出现异常,所以我们必须在Wait前面调用Enter方法或其他获取锁的方法,如lock,这点很重要。对应Enter方法,Monitor给出来另一种实现TryEnter。这两种方法的主要区别在于是否阻塞当前线程,Enter方法在获取不到锁时,会阻塞当前线程直到得到锁。不过缺点是如果永远得不到锁那么程序就会进入死锁状态。我们可以采用Wait来解决,在调用Wait时加入超时时限就可以。

if (Monitor.TryEnter(Names))
{
                Monitor.Wait(Names, 1000); // !! 
                Names.Remove(name); 
                Console.WriteLine("Del: {0}", Names.Count);
                Monitor.Pulse(Names); 
}

互斥锁(lock)

lock关键字是实现线程同步的比较简单的方式,其实就是设置一个临界区。在lock之后的{...}区块为一个临界区,当进入临界区时加互斥锁,离开临界区时释放互斥锁。MSDN对lock关键字的描述是: lock 关键字可将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。

具体例子如下:

static public void ThreadFunc(object name)
        {
            string str = name as string;
            Random rand = new Random();
            int count = rand.Next(100, 200);
            for (int i = 0; i < count; i++)
            {
                lock (NumList)
                {
                    NumList.Add(i);
                    Console.WriteLine("{0} {1}", str, i);
                }
            }
        }

对lock的使用有几点建议:对实例锁定lock(this),对静态变量锁定lock(typeof(val))。lock的对象访问权限最好是private,否则会出现失去访问控制现象。

读写锁(ReadWriteLock)

读写锁的出现主要是在很多情况下,我们读资源的操作要多于写资源的操作。但是如果每次只对资源赋予一个线程的访问权限显然是低效的,读写锁的优势是同时可以有多个线程对同一资源进行读操作。因此在读操作比写操作多很多,并且写操作的时间很短的情况下使用读写锁是比较有效率的。读写锁是一个非静态类所以你在使用前需要先声明一个读写锁对象:

static private ReaderWriterLock _rwlock = new ReaderWriterLock();

读写锁是通过调用AcquireReaderLock,ReleaseReaderLock,AcquireWriterLock,ReleaseWriterLock来完成读锁和写锁控制的

static public void ReaderThread(int thrdId) 
{ 
            try 
            { // 请求读锁,如果100ms超时退出 
                _rwlock.AcquireReaderLock(10); 
                try 
                { 
                    int inx = _rand.Next(_list.Count); 
                    if (inx < _list.Count) 
                        Console.WriteLine("{0}thread {1}", thrdId, _list[inx]); 
                } 
                finally 
                {
                    _rwlock.ReleaseReaderLock(); 
                } 
            } 
            catch (ApplicationException) // 如果请求读锁失败 
            { 
                Console.WriteLine("{0}thread get reader lock out time!", thrdId); 
            } 
        } 
        static public void WriterThread() 
        { 
            try 
            {
                // 请求写锁 
                _rwlock.AcquireWriterLock(100); 
                try 
                { 
                    string val = _rand.Next(200).ToString(); 
                    _list.Add(val); // 写入资源 
                    Console.WriteLine("writer thread has written {0}", val); 
                } 
                finally 
                { // 释放写锁 
                    _rwlock.ReleaseWriterLock(); 
                } 
            } 
            catch (ApplicationException) 
            { 
                Console.WriteLine("Get writer thread lock out time!"); 
            } 
}

如果你想在读的时候插入写操作请使用UpgradeToWriterLock和DowngradeFromWriterLock来进行操作,而不是释放读锁。

static private void UpgradeAndDowngrade(int thrdId) 
        { 
            try 
            { 
                _rwlock.AcquireReaderLock(10); 
                try 
                { 
                    try 
                    {
                        // 提升读锁到写锁 
                        LockCookie lc = _rwlock.UpgradeToWriterLock(100);
                        try
                        {
                            string val = _rand.Next(500).ToString();

                            _list.Add(val); Console.WriteLine
("Upgrade Thread{0} add {1}", thrdId, val); 
                        } 
                        finally
                        { // 下降写锁 
                            _rwlock.DowngradeFromWriterLock(ref lc); 
                        } 
                    } 
                    catch (ApplicationException)
                    { 
                        Console.WriteLine("{0}thread upgrade reader lock failed!", thrdId); 
                    } 
                } 
                finally 
                {
                    // 释放原来的读锁 
                    _rwlock.ReleaseReaderLock();
                }
            } 
            catch (ApplicationException) 
            { 
                Console.WriteLine("{0}thread get reader lock out time!", thrdId);
            }
        }

这里有一点要注意的就是读锁和写锁的超时等待时间间隔的设置。通常情况下设置写锁的等待超时要比读锁的长,否则会经常发生写锁等待失败的情况。

系统内核对象 互斥对象(Mutex)

互斥对象的作用有点类似于监视器对象,确保一个代码块在同一时刻只有一个线程在执行。互斥对象和监视器对象的主要区别就是,互斥对象一般用于跨进程间的线程同步,而监视器对象则用于进程内的线程同步。互斥对象有两种:一种是命名互斥;另一种是匿名互斥。在跨进程中使用到的就是命名互斥,一个已命名的互斥就是一个系统级的互斥,它可以被其他进程所使用,只要在创建互斥时指定打开互斥的名称就可以。在.Net中互斥是通过Mutex类来实现。

其实对于OpenExisting函数有两个重载版本,

Mutex.OpenExisting (String)

Mutex.OpenExisting (String, MutexRights)

对于默认的第一个函数其实是实现了第二个函数 MutexRights.Synchronize|MutexRights.Modify操作。

由于监视器的设计是基于.Net框架,而Mutex类是系统内核对象封装了win32的一个内核结构来实现互斥,并且互斥操作需要请求中断来完成,因此在进行进程内线程同步的时候性能上要比互斥要好。

典型的使用Mutex同步需要完成三个步骤的操作:
1.打开或者创建一个Mutex实例;
2.调用WaitOne()来请求互斥对象;
3.最后调用ReleaseMutex来释放互斥对象。

static public void AddString(string str) 
        {
            // 设置超时时限并在wait前退出非默认托管上下文 
            if (_mtx.WaitOne(1000, true))
            {
                _resource.Add(str); 
                _mtx.ReleaseMutex(); 
            } 
        }

需要注意的是,WaitOne和ReleaseMutex必须成对出现,否则会导致进程死锁的发生,这时系统(.Net2.0)框架会抛出AbandonedMutexException异常。

信号量(Semaphore)

信号量就像一个夜总会:它有确切的容量,并被保镖控制。一旦满员,就没有人能再进入,其他人必须在外面排队。那么在里面离开一个人后,队头的人就可以进入。信号量的构造函数需要提供至少两个参数-现有的人数和最大的人数。

信号量的行为有点类似于Mutex或是lock,但是信号量没有拥有者。任意线程都可以调用Release来释放信号量而不像Mutex和lock那样需要线程得到资源才能释放。

 class SemaphoreTest 
    {
        static Semaphore s = new Semaphore(3, 3); // 当前值=3; 容量=3 
        static void Main() 
        {
            for (int i = 0; i < 10; i++) 
                new Thread(Go).Start(); 
        } 
        static void Go()
        {
            while (true)
            {
                s.WaitOne();
                Thread.Sleep(100); // 一次只有个线程能被处理 
                s.Release(); 
            } 
        }
    }  

事件(ManualResetEvent/AutoResetEvent)

AutoResetEvent

一个AutoResetEvent象是一个"检票轮盘":插入一张通行证然后让一个人通过。"auto"的意思就是这个"轮盘"自动关闭或者打开让某人通过。线程将在调用WaitOne后进行等待或者是阻塞,并且通过调用Set操作来插入线程。如果一堆线程调用了WaitOne操作,那么"轮盘"就会建立一个等待队列。一个通行证可以来自任意一个线程,换句话说任意一个线程都可以通过访问AutoResetEvent对象并调用Set来释放一个阻塞的线程。

如果在Set被调用的时候没有线程等待,那么句柄就会一直处于打开状态直到有线程调用了WaitOne操作。这种行为避免了竞争条件-当一个线程还没来得急释放而另一个线程就开始进入的情况。因此重复的调用Set操作一个"轮盘"哪怕是没有等待线程也不会一次性的让所有线程进入。

WaitOne操作接受一个超时参数-当发生等待超时的时候,这个方法会返回一个false。当已有一个线程在等待的时候,WaitOne操作可以指定等待还是退出当前同步上下文。Reset操作提供了关闭"轮盘"的操作。
AutoResetEvent能够通过两个方法来创建:
1.调用构造函数 EventWaitHandle wh = new AutoResetEvent (false); 如果boolean值为true,那么句柄的Set操作将在创建后自动被调用 ;
2. 通过基类EventWaitHandle方式 EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto); EventWaitHandle构造函数允许创建一个ManualResetEvent。人们应该通过调用Close来释放一个Wait Handle在它不再使用的时候。当在应用程序的生存期内Wait handle继续被使用,那么如果遗漏了Close这步,在应用程序关闭的时候也会被自动释放。

class BasicWaitHandle 
    {
        static EventWaitHandle wh = new AutoResetEvent(false);
        static void Main()
        {
            new Thread(Waiter).Start();
            Thread.Sleep(1000); // 等待一会儿 
            wh.Set(); // 唤醒 
        } 
        static void Waiter()
        {
            Console.WriteLine("Waiting...");
            wh.WaitOne(); // 等待唤醒 
            Console.WriteLine("Notified"); 
        }
    }

ManualResetEvent

ManualResetEvent是AutoResetEvent的一个特例。它的不同之处在于在线程调用WaitOne后不会自动的重置状态。它的工作机制有点象是开关:调用Set打开并允许其他线程进行WaitOne;调用Reset关闭那么排队的线程就要等待,直到下一次打开。可以使用一个带volatile声明的boolean字段来模拟间断休眠 - 通过重复检测标志,然后休眠一小段时间。

ManualResetEvent常常被用于协助完成一个特殊的操作,或者让一个线程在开始工作前完成初始化。

线程池(Thread Pooling)

如果你的应用程序拥有大量的线程并花费大量的时间阻塞在一个Wait Handle上,那么你要考虑使用线程池(Thead pooling)来处理。线程池通过合并多个Wait Handle来节约等待的时间。当Wait Handle被激活时,使用线程池你需要注册一个Wait Handle到一个委托去执行。通过调用ThreadPool.RegisterWaitForSingleObject方法:

 class Test 
    {
        static ManualResetEvent starter = new ManualResetEvent(false); 
        public static void Main()
        {
            ThreadPool.RegisterWaitForSingleObject(starter, Go, "hello", -1, true);
            Thread.Sleep(5000);
            Console.WriteLine("Signaling worker...");
            starter.Set(); 
            Console.ReadLine();
        }
        public static void Go(object data, bool timedOut) 
        {
            Console.WriteLine("Started " + data); // Perform task... 
        }
    }

对于Wait Handle和委托,RegisterWaitForSingleObject接受一个"黑盒"对象并传递给你的委托(就像ParameterizedThreadStart),超时设置和boolean标志指示了关闭和循环的请求。所有进入池中的线程都被认为是后台线程,这就意味着它们不再由应用程序控制,而是由系统控制直到应用程序退出。

注意:如果这时候调用Abort操作,可能会发生意想不到的情况。

你也可以通过调用QueueUserWorkItem方法使用线程池,指定委托并立即被执行。这时你不能在多任务情况下保存共享线程,但是可以得到另外的好处:线程池会保持一个线程的总容量,当作业数超出容量时自动插入任务。

class Test 
    {
        static object workerLocker = new object();
        static int runningWorkers = 100;
        public static void Main() 
        {
            for (int i = 0; i < runningWorkers; i++) 
            {
                ThreadPool.QueueUserWorkItem(Go, i); 
            }
            Console.WriteLine("Waiting for threads to complete..."); 
            lock (workerLocker) 
            {
                while (runningWorkers > 0) 
                    Monitor.Wait(workerLocker);
            }
            Console.WriteLine("Complete!");
            Console.ReadLine(); 
        }
        public static void Go(object instance) 
        {
            Console.WriteLine("Started: " + instance);
            Thread.Sleep(1000); 
            Console.WriteLine("Ended: " + instance); 
            lock (workerLocker)
            {
                runningWorkers--;
                Monitor.Pulse(workerLocker);
            }
        }
    }

为了传递多个对象到目标方法,你必须定义一个客户对象并包含所有属性或通过调用异步的委托。如Go方法接受两参数:

ThreadPool.QueueUserWorkItem (delegate (object notUsed) { Go (23,34); });

其他的方法可以使用异步委托。


.net多线程同步方式总结

在多线程开发中,共享对象的同步是经常遇到的问题,以下总结了C#中线程同步的几种技术:

1,InterLocked原子操作

Decrement(ref int location);递减1

Add(ref int location1, int value);location1+value

Increment(ref int location);递增1

2,Mutex互斥锁

WaitOne(int timeout);等待获取锁

ReleaseMutex();释放锁,记得一定要释放,否则永远被阻塞

3,SemaphoreSlim信号量

SemaphoreSlim(int count);允许的并发线程数量

Wait();等待

Release();释放

4,AutoRestEvent自动重置时间(内核模式)

Set();从一个线程向另一个线程发送通知;

WaitOne();等待通知

5,ManaulResetEventSlim手动重置时间(混合模式)

Wait();等待

Set();通知

Reset();重置

6,CountDownEvent计数事件

CountdownEvent(int count);通知计数

Signal();计数(线程完成一定调用)

Wait();等待

Dispose();释放

7,Barrier

Barrier(int participantCount, Action<Barrier> postPhaseAction);多个线程同步,回调Action

SignalAndWait();执行回调

8,ReaderWriterLockSlim读写锁

EnterReadLock();获取读锁(可共享读)

ExitReadLock();释放读锁

EnterUpgradeableReadLock();获取读锁(可升级到写锁);

ExitUpgradeableReadLock();释放升级读锁

EnterWriteLock();获取写锁(其他线程不可读写)

ExitWriteLock();释放写锁

9,SpinWait自旋等待(混合模式)

SpinOnce();自旋
C#中使用Barrier进行多线程同步:深度解析与实践指南 接下来,我们将通过一个具体的示例来展示如何在C#中使用Barrier进行多线程同步。跟着我一起,一步步来,你会发现这一切并不难。📋。 阅读详情

相关推荐

多线程同步:深入理解同步构造与性能

本文深入探讨了C#中多线程同步的机制和性能,通过代码示例解释了如何使用ManualResetEvent、ManualResetEventSlim、CountdownEvent等同步构造来控制线程的执行。文章还讨论了跨进程EventWaitHandle的创建、等待句柄和延续的使用,以及如何通过WaitAny、WaitAll、SignalAndWait等静态方法来实现更复杂的同步操作。最后,介绍了Barrier类,它允许多线程在特定时间点同步执行。

weixin_29496633的博客 808

.net4.0新特性之线程同步

有时候我们可能需要使用多线程来执行同一任务,这个任务可能包含多步,而每步之间可能并不相干,但是这个任务必须让所有步骤执行完成后才能够进入下一步。这就如同WF中的并行任务。在.net4.0之前我们可能需要几个类来做到同步。但是现在我们只需要1个类就OK。 代码 Console.WriteLine("任务启动"); using (CountdownEvent cd = ne...

diqu1151的博客 190

C#.NET多线程编程全方位实例教程201902

htmltable {th, td {th {pre {简介:本教程旨在通过实例深入解析.NET框架下C#语言的多线程编程,涵盖了从基础使用到互斥的高级概念。介绍了线程创建、同步机制、线程优先级、生命周期,以及线程互斥的策略,包括Monitor和Mutex的使用。此外,还包括了ThreadPool、Task并行库TPL和async/await的高级技术。六个实例演示了如何在实际场景中应用这些技术,帮助开发者理解并掌握多线程编程,提高程序性能。

weixin_28713083的博客 1048

MethodImp属性的用法

MethodImp属性的用法1.MethodImpAttribute说明应用场景 1.MethodImpAttribute说明 指定如何实现方法的详细信息, 此类不能被继承。来自msdn 应用场景 通常Monitor用于同步对类的静态方法或实例方法的访问时,如果临界区跨越整个方法,则可以通过System.Runtime.CompilerServices.MethodImplAttribute将置于...

yjt7755192的博客 500

C#.NET多线程编程与实例教程

本文还有配套的精品资源,点击获取 简介:C#在.NET框架中提供了丰富的多线程支持,本教程将详细介绍如何创建和管理线程,包括线程的基本使用、线程同步与互斥,以及线程安全的数据访问和异步编程模式。具体实现方法包括使用 Thread 类、 Monitor 、 Mutex 、 Semaphore 等关键字和类,以及 ...

weixin_35706067的博客 1357

C#.NET多线程编程实战:6个实例深入理解

简介:C#在.NET框架中提供了强大的多线程编程能力,使得开发者能够利用多核处理器进行并行处理。本资源包含了六个涵盖多线程基础和高级特性的C#.NET实例,如线程创建、线程同步与互斥、线程池使用、线程同步原语、异步编程模型、线程通信、线程状态管理、异常处理、线程局部存储和线程优先级等。开发者通过运行和分析这些实例,可以深入学习和掌握C#.NET多线程编程的实战应用。

weixin_31776191的博客 1368

.NET线程同步的三种方法

线程同步:多个线程同时运行时,可能会同时对同一个内存地址进行写入。同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。 实现同步的三种方法 1.使用lock锁 class ThredTest { static int money = 10000; static object suo = new object(); static void getMoney(string name) { lock (suo) {

舟舟的博客 1575

深入探究C#多线程编程与同步机制

本文还有配套的精品资源,点击获取 简介:C#编程中的多线程是提升应用效率和响应性的关键,它允许同时执行多个任务。本文将介绍如何在C#中创建和管理线程,包括使用 Thread 类和 Task 类,并详细讨论同步机制和相关工具,例如 Monitor 、 Mutex 、 Semaphore 、 Read...

weixin_42573757的博客 1318

C#高级--多线程详解

C#高级–多线程详解 零、文章目录 一、什么是多线程 1、进程 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。 2、线程 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。 3、句柄 句柄是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控制、位图、GDI对象、资源、文件等。 4、多线程 (1)多线程概念 多线程是指程序中

李宥的博客 5万+

C# .NET框架实现简单计算器应用指南

本文还有配套的精品资源,点击获取 简介:本文将指导如何利用C#和.NET框架创建一个具备标准和科学模式的简单计算器。计算器包括图形用户界面,支持键盘输入,以及基本和科学计算功能。通过实践,学习C#编程语言和.NET框架的运用,涉及用户界面设计、事件绑定、计算逻辑实现以及用户偏好存储。 1. C#和.NET框架基础介绍 1.1 C#语言概述 C#(发音为 "...

weixin_30661119的博客 1539

【C#】使用.net 4.0开发的多线程运行示例

本示例使用.net framework 4.0在vs2015上开发完成。 运行为cmd控制台窗口程序。 该示例演示了,1个日志线程,3个任务线程之间的协调工作。 程序可以控制启动线程的是数量,可以控制日志线程启动时间。 示例设置日志线程每个1分钟收集,3个任务线程处理的日志,写入log文件到本地。 3个任务线程在处理完任务后,继续开启新线程进行任务处理,但总数不超过设置的线程阀值。示例阀值为3. 喜欢多线程的朋友,不妨一起探讨。

C#多线程,同步与异步详解

多线程编程是现代编程中非常重要的一部分,它可以提高程序的性能和响应性。在 C# 中,可以通过多种方式创建线程,如使用Thread类、Task类等。线程同步是多线程编程中一个关键问题,可以通过锁、互斥体、信号量等机制来解决。线程安全的集合可以方便地在多线程环境中使用。线程池可以提高线程的复用性,减少线程创建和销毁的开销。异步编程可以进一步提高程序的响应性和性能。在开发多线程程序时,需要注意调试和异常处理,以确保程序的稳定性和可靠性。可能有小伙伴说多线程与异步编程看起来这么相像的,下面来分析下它们的异同点。

m0_57417006的博客 1475

abaqus应力值导出并进行后处理(同一节点多个应力值如何处理?)

Abaqus应力导出: ‘Tools’--- Probe values 在Probe Values里面可以选择需要导出的信息,比如‘Nodes’或者‘Elements’ 如果需要导出多个应力值或者其他信息可以选择‘Components’这个选项。 选择‘Selected’只输出一个正应力S33(对应截图中的) 如果是‘All Direct’,则是输出6个正应力S11, S22, S33, S12, S13, S23 ‘All Principals’输出三个正应力 在输出...

themingyi的博客 5万+

C#基础02-Net框架

对函数式编程更好的支持。

李宥的博客 1184

如何理解Apollo模型参考自适应控制MRAC

最近有了解到百度无人驾驶Apollo项目中的横向控制有用到MRAC(Model Reference Adaptive Control),于是便详细研究了一下这个控制方法,在此总结一下心得。 什么是MRAC MRAC控制系统的基本结构图如下所示: 它由两个环路组成,由控制器和受控对象组成内环,这一部分称之为可调系统,由参考模型和自适应机构组成外环。实际上,该系统是在常规的反馈控制回路上再附加一个参考模型和控制器参数的自动调节回路而形成。在该系统中,参考模型的输出或状态相当于给定一个动态性能指标,(通常,参考模

zmhzmhzm的博客 6480

信奥编程启蒙 3068:【例13.3】时间差

现在给出一个时间,单位是秒,要求按照电子表格式输出,输出保证不会超过 2424 小时。3067:【例13.2】 电子表。把时间转化成电子表格式输出。电子表上的时间显示方法形如。

901

深入理解 C# 与.NET 框架

C# 是由微软开发的一种类型安全、面向对象的编程语言,它融合了 C 和 C++ 的语法风格,并汲取了其他编程语言的优点。C# 旨在为开发者提供一种简洁、高效且易于学习的语言,用于开发各种类型的应用程序。.NET 框架是一个由微软开发的综合性开发平台,它为开发人员提供了一个统一的编程模型,涵盖了多种编程语言(如 C#、Visual Basic.NET 等)。.NET 框架包含了公共语言运行时(CLR)和.NET 类库。C# 与.NET 框架的结合为开发者提供了一个高效、强大的开发平台。

zsnandzmn的博客 1620

什么事LLVM和Clang?Clang与GCC有什么区别?Clang如何使用?

这个问题主要讲Xcode编译器的认知,在学习iOS之前有必要学习一下Objective-C的编译环境。 在了解LLVM和Clang之前,先得了解一下什么是编译器。 编译器是一种计算机程序,它将某种编程语言(高级编程语言)写成的源代码转换成另一种编程语言(低级编程语言)。一个现代编译器的主要工作流程为:源代码->预处理器->编译器->目标代码->链接器->可执行程序...

Welcome~ 9724

c#下实现GUI编程_了解 C# 与 .NET 框架

C# 编程语言C# 一个支持面向对象的编程语言,由微软开发,对就是windows 系统的微软,既然提到微软那你应该会了解到 .NET 框架。   .Net 框架应用程序是多平台的应用程序。框架的设计方式使它适用于下列各种语言:C#、C++、Visual Basic、Jscript、COBOL 等等。所有这些语言可以访问框架,彼此之间也可以互相交互。一个软件不是由一种语言构成的,特别是现在的系...

weixin_39811842的博客 2650

Windows下的Geant4安装教程

本教程将从0开始展示在Windows下如何安装Geant4,包括各种辅助工具(QT、Cmake、Visual Studio)的下载安装,环境变量的配置,以及example的生成等。

weixin_49065278的博客 4111

AutoSar实战营 - 总目录

欢迎大家来学习《AutoSar实战营》专栏,该篇为总目录,方便以后大家直接进入需要学习的文章。 ~正所谓独乐乐不如众乐乐 - - ~1, AutoSar之DaVinci开发环境2,BswM模块之Ecu State Handling1)读写DID功能 《AutoSar实战》读写DID之一:CDD准备 《AutoSar实战》读写DID之二:工具链配置 《AutoSar实战》读写DID之三:代码实现2)功能寻址 《AutoSar实战》禁止功能寻址

weixin_48498880的博客 3633
上一篇: ASP.NET Core 托管和部署(二)【HTTP.sys】
下一篇: 分析.Net里线程同步机制
.NET跨平台
博客等级 码龄15年 6066粉丝 738原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值