关于C#多线程、易失域、锁的分享

时间:2022-09-24 09:12:13

一、多线程

  windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。

  在软件中,如果有一种操作可以被多人同时调用,我们就可以创建多个线程同时处理,以提高任务执行效率。这时,操作就被分配到各个线程中分别执行。

在C#中我们可以使用Thread类和ThreadStart委托,他们都定义在System.Threading命名空间中。

  ThreadStart委托类型用于定义在线程中的工作,就像我们在使用其他的委托类型一样,可以使用方法名来创建此委托类型对象,如“new ThreadStart(test)”

多线程优点:
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程;
(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(4)可以随时停止任务;
(5)可以分别设置各个任务的优先级以优化性能。

多线程缺点:
(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如写文件等。
(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
(3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
(4)对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。

线程生命周期

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

    • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
    • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
    • 不可运行状态:下面的几种情况下线程是不可运行的:
      • 已经调用 Sleep 方法
      • 已经调用 Wait 方法
      • 通过 I/O 操作阻塞
    • 死亡状态:当线程已完成执行或已中止时的状况

Thread 类常用的属性和方法

  关于C#多线程、易失域、锁的分享

关于C#多线程、易失域、锁的分享

最简单的多线程例子,代码如下:

static void Main(string[] agrs)
{
ThreadStart threadWork = new ThreadStart(test);
Thread t1 = new Thread(threadWork);
t1.Name = "t1";
Thread t2 = new Thread(threadWork);
t2.Name = "t2";
Thread t3 = new Thread(threadWork);
t3.Name = "t3";
//开始执行
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
}
static public void test(){
Console.WriteLine("{0},hello,小菜鸟",Thread.CurrentThread.Name);
}

使用多线程另一个重要的问题就是对于公共资源分配的控制,比如,火车的座位是有限的,在不同购票点买票时,就需要对座位资源进行合理分配;在电影院看电影也是这样的,座位只有那么多,我们不可能100个座位卖出200张票,这样是不可以的也是不应该的,那么接下来我们就要看看该如何解决这个问题。

二、易失域

对于类中的成员使用volatile修饰符,它就会被声明为易失域。对于易失域,在多线程环境中,每个线程中对此域的读取(易失读取,volatile read)和写入(易失写入,volatile write)操作都会观察其他线程中的操作,并进行操作的顺序执行,这样就保持易失域使用的一致性了。

volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。多线程的程序,共同访问的内存当中,多个线程都可以操纵,从而无法判定何时这个变量会发生变化

可以这样简单理解:线程是并行的,但对volatile的访问是顺序排除的,避免出现脏值。

理解:

Volatile 字面的意思时易变的,不稳定的。在C#中也差不多可以这样理解。

编译器在优化代码时,可能会把经常用到的代码存在Cache里面,然后下一次调用就直接读取Cache而不是内存,这样就大大提高了效率。但是问题也随之而来了。

在多线程程序中,如果把一个变量放入Cache后,又有其他线程改变了变量的值,那么本线程是无法知道这个变化的。它可能会直接读Cache里的数据。但是很不幸,Cache里的数据已经过期了,读出来的是不合时宜的脏数据。这时就会出现bug。

用Volatile声明变量可以解决这个问题。用Volatile声明的变量就相当于告诉编译器,我不要把这个变量写Cache,因为这个变量是可能发生改变的。

下面贴栗子代码:

using System;
using System.Threading; namespace demoVolatile
{
class Program
{
//多个线程访问的变量,标记为Volatile
//在这里如果不标记可能会卖出不止10张票
volatile static int TicketCount = ;
static void SellTicket()
{
while (TicketCount > )
{
TicketCount--;
Console.WriteLine("{0} 卖出了一张票", Thread.CurrentThread.Name);
}
Console.WriteLine("{0} 下班了", Thread.CurrentThread.Name);
}
static void Main(string[] args)
{
ThreadStart threadWork = new ThreadStart(SellTicket);
Thread t1 = new Thread(threadWork);
t1.Name = "t1";
Thread t2 = new Thread(threadWork);
t2.Name = "t2";
Thread t3 = new Thread(threadWork);
t3.Name = "t3";
//开始执行
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
}
}
}

关于C#多线程、易失域、锁的分享

三、锁

我们都知道,lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。也就是,说在多线程中,使用lock关键字,可以让被lock的对象,一次只被一个线程使用。

lock语句根本使用的就是Monitor.Enter和Monitor.Exit,也就是说lock(this)时执行Monitor.Enter(this),大括号结束时执行Monitor.Exit(this). 也就是说,Lock关键字,就是一个语法糖而已。

使用lock需要注意的地方:
1.lock不能锁定空值某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)
2.lock不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR“暂留”
3.lock锁定的对象是一个程序块的内存边界
4.值类型不能被lock,因为前文标红字的“对象被释放”,值类型不是引用类型的
5.lock就避免锁定public 类型或不受程序控制的对象。

using System;
using System.Threading.Tasks; public class Account
{
private readonly object balanceLock = new object();
private decimal balance; public Account(decimal initialBalance)
{
balance = initialBalance;
} public decimal Debit(decimal amount)
{
lock (balanceLock)
{
if (balance >= amount)
{
Console.WriteLine($"Balance before debit :{balance, 5}");
Console.WriteLine($"Amount to remove :{amount, 5}");
balance = balance - amount;
Console.WriteLine($"Balance after debit :{balance, 5}");
return amount;
}
else
{
return ;
}
}
} public void Credit(decimal amount)
{
lock (balanceLock)
{
Console.WriteLine($"Balance before credit:{balance, 5}");
Console.WriteLine($"Amount to add :{amount, 5}");
balance = balance + amount;
Console.WriteLine($"Balance after credit :{balance, 5}");
}
}
} class AccountTest
{
static void Main()
{
var account = new Account();
var tasks = new Task[];
for (int i = ; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => RandomlyUpdate(account));
}
Task.WaitAll(tasks);
} static void RandomlyUpdate(Account account)
{
var rnd = new Random();
for (int i = ; i < ; i++)
{
var amount = rnd.Next(, );
bool doCredit = rnd.NextDouble() < 0.5;
if (doCredit)
{
account.Credit(amount);
}
else
{
account.Debit(amount);
}
}
}
}

作用:当同一个资源被多个线程读,少个线程写的时候,使用读写锁

引用:https://blog.csdn.net/weixin_40839342/article/details/81189596

问题: 既然读读不互斥,为何还要加读锁

答:     如果只是读,是不需要加锁的,加锁本身就有性能上的损耗

如果读可以不是最新数据,也不需要加锁

如果读必须是最新数据,必须加读写锁

读写锁相较于互斥锁的优点仅仅是允许读读的并发,除此之外并无其他。

注意:不要使用ReaderWriterLock,该类有问题

ok,今天的分享就到这里了,如有错误的地方请指出,谢谢。

关于C#多线程、易失域、锁的分享的更多相关文章

  1. C&num;易失域、锁的分享,多线程

    C#多线程.易失域.锁的分享 一.多线程 windows系统是一个多线程的操作系统.一个程序至少有一个进程,一个进程至少有一个线程.进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公 ...

  2. Netty:一种非易失堵塞client&sol;server相框

    Netty:一种非易失堵塞client/server相框 作者:chszs.转载需注明.博客主页:http://blog.csdn.net/chszs Netty是一个异步事件驱动的网络应用框架,为J ...

  3. C&num;多线程编程中的锁系统

    C#多线程编程中的锁系统(二) 上章主要讲排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了. 这一次我们说说升级锁和原子操作. 目录 1:volatile 2:  Inter ...

  4. c&num;中多线程同步Lock(锁)的研究以及跨线程UI的操作

    本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做demo的时候,就把多线程通过事件操作UI的代码也写了出来,留作备忘和分享吧. 其实多线程的同步,使用 ...

  5. 易通电脑锁2007V6&period;3&period;3&period;3无法卸载问题解决办法

    易通电脑锁2007V6.3.3.3无法卸载问题解决办法把原版文件拷贝回去.bat@echo offcolor 2Fecho 该批处理会把易通电脑锁2007版原文件拷贝回去,解决易通电脑锁卸载时出现的运 ...

  6. Non-Volatile Register 非易失性寄存器 调用约定对应寄存器使用

    非易失性寄存器(Non-volatile register)是它的内容必须通过子程序调用被保存的一个寄存器.如果一个程序改变了一个非易失性寄存器的值,它必须保存在改变这个寄存器之前堆栈中保存旧的值和在 ...

  7. Java多线程6:Synchronized锁代码块(this和任意对象)

    一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...

  8. Java多线程5:Synchronized锁机制

    一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...

  9. 网络编程之多线程——GIL全局解释器锁

    网络编程之多线程--GIL全局解释器锁 一.引子 定义: In CPython, the global interpreter lock, or GIL, is a mutex that preven ...

随机推荐

  1. 移动端web开发——视口

    本篇主要是记录一下移动端视口的分类说明和其它的一些知识.在开始之前,先看一个典型的例子: <meta name="viewport" content="width= ...

  2. LINQ系列:Linq to Object分组操作符

    分组是指根据一个特定的值将序列中的值或元素进行分组.LINQ只包含一个分组操作符:GroupBy. GroupBy 1>. 原型定义 public static IQueryable<IG ...

  3. Babel下的ES6兼容性与规范

    前端开发 Babel下的ES6兼容性与规范   ES6标准发布后,前端人员也开发渐渐了解到了es6,但是由于兼容性的问题,仍然没有得到广泛的推广,不过业界也用了一些折中性的方案来解决兼容性和开发体系问 ...

  4. PowerDesigner打开设计文件后提示failed to read the fileXXX的解决办法

    擦,一身盗汗.一向的设计信息都在设计图里!竟然坏了,坏了!!!!! 惊.怒.悲 固然可以经由过程数据库当前状况反向工程.然则那么注解.我写的提示这些器材都邑消散. 比来的备份是10天前,恢复也会有必然 ...

  5. 3月3日 Mark

    感觉LeetCode OJ 水题较多... 不过回复基础就是这样吧.. 刚刚和Gaewah聊了下,后续可以考虑去做Topcoder的SRM或者codeforces,Mark.

  6. STL底层数据结构实现

    C++ STL 的实现: 1.vector      底层数据结构为数组 ,支持快速随机访问 2.list            底层数据结构为双向链表,支持快速增删 3.deque       底层 ...

  7. 标签(Label、JLabel)

    构造函数 Label( ) Label(String str) Label(String str, int how) 第一种形式生成一个空白标签:第二种形式生成一个包含由参数str所设定的字符串的标签 ...

  8. 微信小程序http 400问题

    在v0.14.140900版本的wechat小程序开发工具中做网络请求,直接使用微信的网络请求代码debug过程中发生了400 (Bad request)错误. wx.request({ url: ' ...

  9. python创建&lowbar;&lowbar;init&period;py&lowbar;&lowbar;文件导入模块仍然报错ModuleNotFoundError&colon; No module named 'name'

    今自定义模块后非相同目录导出提示找不到模块报错信息如下: ModuleNotFoundError: No module named 'name' 各方查找各位大神方法很多 参考链接 1.在需要导入的文 ...

  10. BZOJ2599&colon;&lbrack;IOI2011&rsqb;Race&lpar;点分治&rpar;

    Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k 第二 ...