C#的线程同步「建议收藏」

后端 (97) 2023-03-28 19:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说C#的线程同步「建议收藏」,希望能够帮助你!!!。
C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第1张

使用线程有几个原因。假设从应用程序中进行网络调用需要一定的时间。我们不希望用户界面停止响应,让用户一直等待直到从服务器返回一个响应。用户可以同时执行其他一些操作,或者甚至取消发送给服务器的请求。这些都可以使用线程来实现。

线程是程序中独立的指令流。使用C编写任何程序时,都有一个入口点:Main()方法。程序从Main(方法的第一条语句开始执行,直到这个方法返回为止。

这种程序结构非常适合于其中有一个可识别的任务序列的程序,但程序常常需要同时完成多个任务。线程对客户端和服务器端应用程序都非常重要。在Visual Studio 编辑益中输入.#码则,尔统会分析代码,用下划线标出遗漏的分号或其他语法错误,这用一个后台线程完成。Microsoft Word的拼写检查器也会做相同的事。一个线程等待用户输入,而另一个线程进行后台搜索。第3个线程将写入的数据存储在临时文件中,第4个线程从 Internet上下载其他数据。

运行在服务器上的应用程序中,等待客户请求的线程,称为侦听器线程。只要接收到请求,就把它传递给另一个工作线程,之后继续与客户通信。侦听器线程会立即返回,接收下一个客户发送的下一个请求。

进程包含资源,如 Window句柄、文件系统句柄或其他内核对象。每个进程都分配了虚拟内存。一个进程至少包含一个线程。操作系统会调度线程。线程有一个优先级、实际上正在处理的程序的位置计数器、一个存储其局部变量的栈。每个线程都有自己的栈,但程序代码的内存和堆由一个进程的所有线程共享。这使一个进程的所有线程之间的通信非常快——该进程的所有线程都寻址相同的虚拟内存。但是,这也使处理比较困难,因为多个线程可以修改同一个内存位置。

为编写能够利用并行性的代码,必须区分两种主要的场景:任务并行性和数据并行性。对于任务并行性,使用CPU的代码被并行化。CPU的多个核心会被利用起来,更快速地完成包含多个任务的活动,而不是在一个核心中按顺序一个一个地执行任务。对于数据并行性,则使用了数据集合。在集合上执行的工作被划分为多个任务。当然,任务并行性和数据并行性可以混合起来。


如果两个或多个线程访问相同的对象,并且对共享状态的访问没有同步就会出现争用条件。

要避免该问题,可以锁定共享的对象。这可以在线程中完成:用下面的 lock语句锁定在线程中共享的 state变量。只有一个线程能在锁定块中处理共享的state对象。由于这个对象在所有的线程之间共享,因此如果一个线程锁定了state,另一个线程就必须等待该锁定的解除。一旦接受锁定,线程就拥有该锁定,直到该锁定块的末尾才解除锁定。如果改变state变量引用的对象的每个线程都使用一个锁定,就不会出现争用条件。

C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第2张

在使用共享对象时,除了进行锁定之外,还可以将共享对象设置为线程安全的对象。在下面的代码中,ChangeState()方法包含一条lock语句。由于不能锁定 state变量本身(只有引用类型才能用于锁定),因此定义一个object类型的变量sync,将它用于 lock语句。如果每次state 的值更改时,都使用同一个同步对象来锁定,就不会出现争用条件。

C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第3张


线程同步

要避免同步问题,最好不要在线程之间共享数据。当然,这并不总是可行的。如果需要共享数据,就必须使用同步技术,确保一次只有一个线程访问和改变共享状态。注意,同步问题与争用条件和死锁有关。如果不注意这些问题,就很难在应用程序中找到问题的原因,因为线程问题是不定期发生的。

Lock类

C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第4张


Semaphore类

信号量非常类似于互斥,其区别是,信号量可以同时由多个线程使用。信号量是一种计数的互斥锁定。使用信号量,可以定义允许同时访问受旗语锁定保护的资源的线程个数。如果需要限制可以访问可用资源的线程数,信号量就很有用。例如,如果系统有3个物理端口可用,就允许3个线程同时访问IO端口,但第4个线程需要等待前3个线程中的一个释放资源。

.NET 4.5为信号量功能提供了两个类Semaphore和SemaphoreSlim。Semaphore类可以命名,使用系统范围内的资源,允许在不同进程之间同步。SemaphoreSlim类是对较短等待时间进行了优化的轻型版本。

在下面的示例应用程序中,在 Main()方法中创建了6个任务和一个计数为3的信号量。在Semaphore类的构造函数中,定义了锁定个数的计数,它可以用信号量(第二个参数)来获得,还定义了最初释放的锁定数(第一个参数)。如果第一个参数的值小于第二个参数,它们的差就是已经分配线程的计数值。与互斥一样,也可以给信号量指定名称,使之在不同的进程之间共享。这里定义信号量时没有指定名称,所以它只能在这个进程中使用。在创建了SemaphoreSlim对象之后,启动6个任务,它们都获得了相同的信号量。

C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第5张


在任务的主方法 TaskMain()中,任务利用Wait()方法锁定信号量。信号量的计数是3,所以有3个任务可以获得锁定。第4个任务必须等待,这里还定义了最长的等待时间为600毫秒。如果在该等特时间过后未能获得锁定。任务就把一条消息写入控制台,在循环中继续等待。只要获得了锁定,任务就把一条消息写入控制台,睡眠一段时间,然后解除锁定。在解除锁定时,在任何情况下一定要解除资源的锁定。这一点很重要。这就是在firnally处理程序中调用Semaphore类的ReleaseO)方法的原因。

C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第6张

 C#的线程同步「建议收藏」_https://bianchenghao6.com/blog_后端_第7张

发表回复