目录
TcpClient、TcpListener和Socket的深入比较
同步lock锁,为什么一般是对象,不是值类型或字符串
lock值类型
如 lock(1),本质上是Monitor.Enter,Monitor.Enter会使值类型装箱,每次lock的是装箱后的对象,每次锁住的对象不一样,也就是说每次都会判断成为申请互斥锁,别的线程照样能够访问里面的代码,达不到同步效果。
lock字符串
锁定字符串非常危险,因为字符串被公共语言运行库(CLR)"暂存",若字符串存在再“字符串常量池”中,则不会重新分配内存,则同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内存的字符串放置了锁,就将锁定应用程序中该字符串的所有实例。
推荐的lock对象
通常最好避免锁定public类型或锁定不受应用程序控制的对象实例,例如,如果该实例可以被公开访问,则lock(this)可能会因为不受控制的代码导致该对象被锁住,可能导致死锁,既两个或更多线程等待释放同一个对象。同理,锁定公共数据类型也可能导致此问题。而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步效果。推荐定义
private static readonly object obj = new object();
string操作到底创建了几个string对象
1.string[] str1 = new string[1]{“abc”};创建了几个对象?
答案:1个或2个
分析:
- 第一个,先查找常量池是否有“abc”,如果没有,则在常量池中创建一个‘abc’对象,若果有,则不创建。
- 第二个,new 本身就要在堆中开辟一块内存,创建一个‘abc’对象实例,并返还地址给栈中的str,如下图所示
2.string str2 = “a” + “b” + “d”; 创建了几个对象?
答案:1个
分析:
- 没有new,则堆中创建对象为0
- 按照第一个问题的推理,‘a’,‘b’,‘d’三个在常量池中都没有,是不是应该创建3个呢?这里有个小知识点,这样的代码,在编译时就已经把abd这是三个常量拼在一起了,所以只在常量池中创建一个‘abd’的对象。
3. str2="abc";创建了几个对象?
答案:0个或1个
分析:
- 常量池中有,则返回内存地址,创建0个对象
- 常量中没有,则创建1个对象
4.str = "abc" + "def";创建了几个对象?
答案:0或1个
分析:
- 在编译时就已经把abcdef这是三个常量拼在一起了,所以只在常量池中创建一个‘abcdef’的对象。
- 常量池存在,则无需创建,0个对象
5.str = "abc" + new String("def");创建了几个对象?
答案:创建了4个字符串对象(常量池中分别有“abc”和“def”,堆中对象new String(“def”)和“abcdef”)和1个StringBuilder对象
注:
常量池:CLR为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池。字符串常量池不在堆中也不在栈中,是独立的内存空间管理,在内存的常量区。
- 不是所有的字符串都放在暂存池中,运行时期动态创建的字符串不会被加入到暂存池
- 主驻留池由CLR来维护,其中的所有字符串对象的值都不相同。
- 只有编译阶段的文本字符常量会被自动添加驻留池
- string.intern()可以把动态创建的字符串加入到驻留池
string和stringbuilder有什么区别?
- string 类型是不可变的,一单创建了一个字符串对象,他的内容就不能被修改。任何对字符的更改操作都会产检一个新的字符串对象。
- stringbuilder类型是可变的,他允许对字符串进行动态修改二不会创建新的对象。若需要频繁修改字符串时,stringbuilder通常比string更有效率。
- string是不可变的,对字符串频繁的修改操作会导致频繁的创建新的字符串对象,可能导致内存分配和性能开销。
- stringbuilder避免了频繁的内存分配,是在需要时动态的增加容量,从而提高字符串操作的性能。
什么是依赖注入,如何实现?
试试 依赖注入(简称DI)是一种软件设计模式,用于实现对象之间的松耦合关系。在依赖注入中,对象不在负责创建他所依赖的对象,而是通过外部注入的方式来获取这些依赖。这样做的好处是可以更容易的测试,替换和重用组件,同时降低了组件之间的耦合性。实现方式:
- 构造函数注入:这是最常用的注入类型。在构造函数注入中,我们可以将依赖想传递给构造函数。
public class MyClass
{
private readonly IDependency _dependency;
public MyClass(IDependency dependency)
{
_dependency = dependency;
}
// 使用_dependency
}
- 属性注入:通过公开可写属性来注入依赖性。这种方式相对于构造函数注入来说,灵活性差且容易被忽略。
public class MyClass
{
public IDependency Dependency { get; set; }
// 使用Dependency
}
- 方法注入:通过方法参数注入依赖。这种方式通常用于需要在对象创建后才注入依赖的情况。
public class MyClass { public void Method(IDependency dependency) { // 使用dependency } }
using使用
- 命名空间引用
- 资源管理:using语句可用于管理资源,如文件,数据库连接,网络连接等。当using结束时,会自动调用对象的Dispose方法,确保资源得到释放。
using (var fileStream = new FileStream("example.txt", FileMode.Open)) { // 使用 fileStream 读取文件内容 } // 在此之后,fileStream 已经被自动释放
struct 和class有什么区别
-
struct继承自System.Value类型,因此是值类型。当数据量较小时,结构体更可取。结构体不能抽象,无需使用new关键字创建对象,struct无法创建任何默认构造函数。不能继承
-
class 为引用类型,允许继承和抽象
-
struct 类型实例化在未初始化时会总动赋予默认值,而class类型实例在未初始化是会被置为null
使用场景:
通常情况下,如果需要穿件轻量级的,不可变的数据类型,或者希望避免引用类型的复杂性和性能开销,可以使用struct;而对于大对象,需要继承和多态的场景,则应该使用class
throw和throw ex区别
throw语句将保留前一个函数的原始错误堆栈,而throw ex语句将保留从抛出点开始的堆栈跟踪。通常建议使用throw,因为他提供了准确的错误信息和跟踪数据。
进程间通讯方式
1.管道(Pipe):
- 匿名管道:通常用于父子进程之间的通讯,是一种单向通讯方式。
- 命名管道(FIFO):允许无关的进程之间进行通讯,通过文件系统中的一个特殊文件来实现。
4.2.消息队列(Message Queues):
-
MSMQ 进程可以通过消息队列发送和接收消息,通常在内核中维护一个消息队列列表,进程可以将消息写入队列或从队列中读取消息。
3.共享内存(Shared Memory):
- 两个或多个进程可以访问同一块物理内存区域,实现数据共享。通常需要使用信号量或其他同步机制来控制对共享内存的访问。
4.信号(Signals):
- 进程可以向另一个进程发送信号,用于通知某个事件的发生或请求某种操作。
5.套接字(Sockets):
- 基于网络的进程间通讯方式,可以在同一台计算机或不同计算机上的进程之间进行通信。
6.文件(File):
- 进程可以通过读写文件的方式进行通信,可以是普通文件或者特殊的设备文件。
7.RPC(远程过程调用):
- 允许一个进程调用另一个进程中的过程或函数,使得像调用本地过程一样调用远程过程。
8.内核同步原语:
- 例如信号量、互斥锁、条件变量等,可以在进程之间实现同步和互斥。
常用的数据结构及插入删除的时间复杂度
数据结构 | 插入 | 查询 | 删除 |
Array | O(n) | O(n) | O(n) |
LinkList<T> | O(1) | O(n) | O(n) |
List<T> | O(1)或O(N) | O(n) | O(n) |
Stack<T> | O(1) | - | O(1) |
Queue<T> | O(1) | - | O(1) |
Dictionary<K,V> | O(1) | Containskey:O(1) ContainsValue(n) | O(1) |
SortedList<K,V) | O(n) | O(n) | O(n) |
SortedDictionary<k,v> | O(lg n) | ContainsKey:O(lg n) ContainsValue:O(n) | O(lg n) |
HashSet<T> | O(1) 负载因子冲突:O(n) | O(1) 负载因子冲突:O(n) | O(1) 负载因子冲突:O(n) |
SortedSet | O(log n) | O(log n) | O(log n) |
Heap | O(log n) | O(1) | O(log n) |
异步和线程的关系
1.线程
- 线程是操作系统中能够独立执行的最小单位。他负责在cpu上执行指令,一个进程可以包含一个或多个线程。
- 线程可以是同步(执行过程中阻塞调用线程)或异步的(不会阻塞调用线程)。
2.异步
- 异步编程是一种编程模型 ,允许程序在等待某些操作完成时继续执行其他操作,而不必阻塞等待。
- 异步通常涉及到I/O(如网络请求、文件读写等)。
关系:
- 异步编程通过将耗时的操作交给其他线程或者系统来处理,是的主线程调用线程可以继续执行其他任务,提高了程序的并发性和响应性。
- 实现异步操作时,尝尝会使用线程池,任务调度器等机制来管理和执行异步任务,这些任务可以在不同的线程上执行,而不是阻塞当前线程。
tcp、socket、http的区别及使用场景
1.TCP (Transmission Control Protocol):
- TCP是一种面向连接的,可靠的传输协议,它负责在网络中可靠地传输数据流。
- TCP提供了可靠的数据传输,流量控制,拥塞控制等功能,适用于对数据传输的可靠性要求较高的场景。
- TCP连接是双向的,数据传输是全双工的,可以实现双向通信。
- TCP 是OSI模型中传输层的一部分,位于IP层之上,应用于各种网络应用中,如web服务、电子邮件、文件传输等。
2. Socket:
- Socket是在网络通信中国用于实现端到端编程接口。
- Socket API 提供了一组用于创建网络套现字用于网络套接字、发送和接收数据等操作函数。
- socket 可以基于tcp或udp进行通讯,可以用于实现各种网络应用,如客户端-服务器通信,P2P通信等。
3. HTTP (Hypertext Transfer Protocol):
- HTTP 是一种基于 TCP 协议的应用层协议,用于在客户端和服务器之间传输超文本文档。
- HTTP 是一种无状态协议,每个请求都是独立的,服务器不会保存客户端的状态信息。
- HTTP 使用请求-响应模型,客户端发送请求给服务器,服务器处理请求并返回响应给客户端。
- HTTP常用于Web应用中,用于浏览器和服务器之间的通信,如网页浏览、RESTful API等。
使用场景:
- TCP适用于需要可靠数据传输和双向通信的场景,如文件传输、远程登录等。
- Socket适用于实现各种自定义网络协议的场景,可以基于 TCP 或 UDP 进行通信。
- HTTP适用于Web应用中,用于客户端和服务器之间的通信,如网页浏览、Web服务等。
TcpClient、TcpListener和Socket的深入比较
1.Socket:
Socket
是.NET框架提供的最底层的网络通信类,它提供了对套接字的封装,可以直接操作套接字进行网络通信。Socket
类提供了丰富的方法和属性,可以实现各种高级的网络通信功能,如设置超时、异步操作等。- 使用
Socket
类可以实现高度定制化的网络通信,但同时也需要更多的代码和工作量。
示例:
服务端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketUtil
{
public class SocketServer
{
private string _ip = string.Empty;
private int _port = 0;
private Socket _socket = null;
private byte[] buffer = new byte[1024 * 1024 * 2];
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">监听的IP</param>
/// <param name="port">监听的端口</param>
public SocketServer(string ip, int port)
{
this._ip = ip;
this._port = port;
}
public SocketServer(int port)
{
this._ip = "0.0.0.0";
this._port = port;
}
public void StartListen()
{
try
{
//1.0 实例化套接字(IP4寻找协议,流式协议,TCP协议)
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.0 创建IP对象
IPAddress address = IPAddress.Parse(_ip);
//3.0 创建网络端口,包括ip和端口
IPEndPoint endPoint = new IPEndPoint(address, _port);
//4.0 绑定套接字
_socket.Bind(endPoint);
//5.0 设置最大连接数
_socket.Listen(int.MaxValue);
Console.WriteLine("监听{0}消息成功", _socket.LocalEndPoint.ToString());
//6.0 开始监听
Thread thread = new Thread(ListenClientConnect);
thread.Start();
}
catch (Exception ex)
{
}
}
/// <summary>
/// 监听客户端连接
/// </summary>
private void ListenClientConnect()
{
try
{
while (true)
{
//Socket创建的新连接
Socket clientSocket = _socket.Accept();
clientSocket.Send(Encoding.UTF8.GetBytes("服务端发送消息:"));
Thread thread = new Thread(ReceiveMessage);
thread.Start(clientSocket);
}
}
catch (Exception)
{
}
}
/// <summary>
/// 接收客户端消息
/// </summary>
/// <param name="socket">来自客户端的socket</param>
private void ReceiveMessage(object socket)
{
Socket clientSocket = (Socket)socket;
while (true)
{
try
{
//获取从客户端发来的数据
int length = clientSocket.Receive(buffer);
Console.WriteLine("接收客户端{0},消息{1}", clientSocket.RemoteEndPoint.ToString(), Encoding.UTF8.GetString(buffer, 0, length));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
break;
}
}
}
}
}
客户端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SocketUtil
{
public class SocketClient
{
private string _ip = string.Empty;
private int _port = 0;
private Socket _socket = null;
private byte[] buffer = new byte[1024 * 1024 * 2];
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">连接服务器的IP</param>
/// <param name="port">连接服务器的端口</param>
public SocketClient(string ip, int port)
{
this._ip = ip;
this._port = port;
}
public SocketClient(int port)
{
this._ip = "127.0.0.1";
this._port = port;
}
/// <summary>
/// 开启服务,连接服务端
/// </summary>
public void StartClient()
{
try
{
//1.0 实例化套接字(IP4寻址地址,流式传输,TCP协议)
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.0 创建IP对象
IPAddress address = IPAddress.Parse(_ip);
//3.0 创建网络端口包括ip和端口
IPEndPoint endPoint = new IPEndPoint(address, _port);
//4.0 建立连接
_socket.Connect(endPoint);
Console.WriteLine("连接服务器成功");
//5.0 接收数据
int length = _socket.Receive(buffer);
Console.WriteLine("接收服务器{0},消息:{1}", _socket.RemoteEndPoint.ToString(), Encoding.UTF8.GetString(buffer,0,length));
//6.0 像服务器发送消息
for (int i = 0; i < 10; i++)
{
Thread.Sleep(2000);
string sendMessage = string.Format("客户端发送的消息,当前时间{0}", DateTime.Now.ToString());
_socket.Send(Encoding.UTF8.GetBytes(sendMessage));
Console.WriteLine("像服务发送的消息:{0}", sendMessage);
}
}
catch (Exception ex)
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
Console.WriteLine(ex.Message);
}
Console.WriteLine("发送消息结束");
Console.ReadKey();
}
}
}
开启客户端和服务端
using SocketUtil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SocketClientApp
{
class Program
{
static void Main(string[] args)
{
SocketClient client = new SocketClient(8888);
client.StartClient();
Console.ReadKey();
}
}
}
using SocketUtil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SocketServerApp
{
class Program
{
static void Main(string[] args)
{
SocketServer server = new SocketServer(8888);
server.StartListen();
Console.ReadKey();
}
}
}
2.TcpClient:
TcpClient
是.NET框架中用于客户端TCP通信的类,它封装了底层的Socket
,提供了更高级别的接口。TcpClient
提供了连接到远程主机、发送和接收数据等简单易用的方法,适合用于实现客户端程序。- 通过
TcpClient
类,可以快速地建立TCP连接,并进行数据交换,而不需要深入了解底层的网络通信细节。
3. TcpListener:
TcpListener
是.NET框架中用于服务器端TCP通信的类,它监听指定的端口,等待客户端的连接请求。TcpListener
封装了底层的Socket
,提供了简单易用的方法,用于监听和接受客户端连接。- 通过
TcpListener
类,可以方便地创建TCP服务器,并接受客户端的连接,然后通过TcpClient
进行数据交换。
示例:
服务端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class TcpServer
{
private TcpListener tcpListener;
private Thread listenThread;
private int heartbeatInterval;
public TcpServer(int port, int heartbeatIntervalInSeconds)
{
IPAddress ipAddress = IPAddress.Any;
tcpListener = new TcpListener(ipAddress, port);
heartbeatInterval = heartbeatIntervalInSeconds * 1000;
}
public void Start()
{
tcpListener.Start();
Console.WriteLine("服务端已启动,等待客户端连接...");
listenThread = new Thread(new ThreadStart(ListenForClients));
listenThread.Start();
}
private void ListenForClients()
{
while (true)
{
TcpClient client = tcpListener.AcceptTcpClient();
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientCommunication));
clientThread.Start(client);
}
}
private void HandleClientCommunication(object client)
{
TcpClient tcpClient = (TcpClient)client;
string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();
Console.WriteLine("客户端 {0} 已连接。", clientEndPoint);
NetworkStream clientStream = tcpClient.GetStream();
byte[] buffer = new byte[4096];
while (true)
{
try
{
int bytesRead = clientStream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
Console.WriteLine("客户端 {0} 已断开连接。", clientEndPoint);
break;
}
// 处理客户端发送的数据(此处略,根据实际需求处理数据)
}
catch (Exception ex)
{
Console.WriteLine("与客户端 {0} 的连接发生异常: {1}", clientEndPoint, ex.Message);
break;
}
}
tcpClient.Close();
}
}
客户端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class TcpClientWithHeartbeat
{
private TcpClient tcpClient;
private int heartbeatInterval;
public TcpClientWithHeartbeat(int heartbeatIntervalInSeconds)
{
heartbeatInterval = heartbeatIntervalInSeconds * 1000;
}
public void Connect(string ipAddress, int port)
{
tcpClient = new TcpClient();
tcpClient.Connect(IPAddress.Parse(ipAddress), port);
Console.WriteLine("与服务端连接成功!");
// 启动心跳发送线程
Thread heartbeatThread = new Thread(new ThreadStart(SendHeartbeat));
heartbeatThread.Start();
}
private void SendHeartbeat()
{
while (true)
{
try
{
if (tcpClient.Connected)
{
// 编码心跳消息
string heartbeatMessage = "heartbeat";
byte[] heartbeatData = Encoding.ASCII.GetBytes(heartbeatMessage);
// 发送心跳消息给服务端
tcpClient.GetStream().Write(heartbeatData, 0, heartbeatData.Length);
}
else
{
// 客户端已断开连接
Console.WriteLine("与服务端的连接已断开。");
break;
}
// 暂停指定的时间间隔(60秒)
Thread.Sleep(heartbeatInterval);
}
catch (Exception ex)
{
// 发生异常,通常表示客户端断开连接
Console.WriteLine("发送心跳数据给服务端发生异常: {0}", ex.Message);
break;
}
}
}
}
总结:
- 如果你需要一个简单的封装来处理TCP协议操作,且不需要控制底层细节,可以选择使用TcpClient类。它是面向应用程序逻辑的,使得开发人员可以专注于业务逻辑而不是网络通信细节。
- 如果你正在开发服务器端应用程序,需要监听传入的连接请求,那么可以使用TcpListener类。它简化了服务器端的编程模型,使得处理并发连接更加简单。
- 如果你需要更低级别的控制或者需要使用非TCP协议(如UDP),那么可以使用Socket类。它提供了更多的灵活性和性能,但也需要更多的编程知识来正确地使用它。
死锁、如何避免死锁
什么是死锁
死锁是指由于两个或多个线程互相持有对方所需的资源,并且互相等待对方释放资源,导致这些线程都处于等待状态,无法继续执行。如果线程都不主动释放资源,将产生死锁。
产生死锁的条件
- 互斥条件:线程对于所有分配到的资源具有排他性,既一个资源只能被一个线程占用,直到被该线程释放。
- 请求和保持条件:一个线程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
- 不剥夺条件:任何一个资源在没有被线程释放之前,其他线程都无法对他剥夺占用。
- 循环等待条件:当发生死锁时,锁等待的线程形成一个环路,造成永久阻塞。
死锁示例1:
var resetEventA = new ManualResetEvent(false);
var resetEventB = new ManualResetEvent(false);
var taskA = new Task(() =>
{
Console.WriteLine("TaskA start");
resetEventA.WaitOne(); //等待resetEventA被释放
resetEventB.Set();//resetEventB在此处被释放
Console.WriteLine("TaskA done");
Thread.Sleep(5000);
});
var taskB = new Task(() =>
{
Console.WriteLine("TaskB start");
resetEventB.WaitOne();//等待resetEventB被释放
resetEventA.Set();//resetEventA在此处被释放
Console.WriteLine("TaskB done");
Thread.Sleep(5000);
});
Parallel.Invoke(()=>taskA.Start(),()=>taskB.Start());
taskA.Wait();
taskB.Wait();
示例1 运行后,永远没有输出结果“TaskA done”,发生了死锁。线程taskA等待resetEventA释放,线程taskB等待resetEventB释放,双方都在等在对方释放资源,导致循环等待从而产生死锁。
示例2
class Program
{
static void Main(string[] args)
{
var workers = new Workers();
workers.StartThreads();
var output = workers.GetResult();
Console.WriteLine(output);
}
}
class Workers
{
Thread thread1, thread2;
object resourceA = new object();
object resourceB = new object();
string output;
public void StartThreads()
{
thread1 = new Thread(Thread1DoWork);
thread2 = new Thread(Thread2DoWork);
thread1.Start();
thread2.Start();
}
public string GetResult()
{
thread1.Join();
thread2.Join();
return output;
}
public void Thread1DoWork()
{
lock (resourceA)
{
Thread.Sleep(100);
lock (resourceB)
{
output += "T1#";
}
}
}
public void Thread2DoWork()
{
lock (resourceB)
{
Thread.Sleep(100);
lock (resourceA)
{
output += "T2#";
}
}
}
}
示例运行后永远没有输出结果,发生了死锁。线程 1 工作时锁定了资源 A,期间需要锁定使用资源 B;但此时资源 B 被线程 2 独占,恰巧资线程 2 此时又在待资源 A 被释放;而资源 A 又被线程 1 占用......,如此,双方陷入了永远的循环等待中。
如何避免死锁
- 按相同的顺序获取锁:如果你的程序中使用了多个锁,并且一个线程需要获取多个锁来执行任务,确保所有线程以相同的顺序获取锁。这可以避免死锁。
- 避免嵌套锁:在一个锁的范围内尝试获取另一个锁会增加死锁的风险。尽量避免在一个锁的作用域内再次获取另一个锁。
- 避免长时间持有锁:避免在锁的范围内执行长时间的操作,尽可能减少持有锁的时间。长时间持有锁会增加其他线程等待该锁的时间,从而增加发生死锁的可能性。
- 使用超时:在获取锁的时候,可以使用
Monitor.TryEnter()
或Mutex.WaitOne()
方法,并传递一个超时时间,以避免无限期等待锁的释放。当等待超时时,线程可以执行一些其他的操作,或者回退并尝试后续的操作。 - 避免多个资源间的循环依赖:如果多个线程分别持有不同的资源,并且彼此需要对方的资源才能继续执行,就可能发生死锁。尽量设计你的程序,避免这种循环依赖。
- 使用并发集合:在多线程环境中,使用并发集合(如
ConcurrentDictionary
、ConcurrentQueue
等)可以减少对锁的需求,从而降低死锁的风险。 - 使用适当的同步策略:根据具体的情况选择适当的同步机制,如
lock
关键字、Monitor
类、Mutex
、Semaphore
等。每种同步机制都有自己的特点和适用场景。
上述示例2可通过增加等待时间后,自动释放解决死锁问题。
var resetEventA = new ManualResetEvent(false);
var resetEventB = new ManualResetEvent(false);
var taskA = new Task(() =>
{
Console.WriteLine("TaskA start");
resetEventA.WaitOne(2000); //等待2秒后resetEventA被释放
resetEventB.Set();//resetEventB在此处被释放
Console.WriteLine("TaskA done");
Thread.Sleep(5000);
});
var taskB = new Task(() =>
{
Console.WriteLine("TaskB start");
resetEventB.WaitOne(2000);//等待2秒后resetEventB被释放
resetEventA.Set();//resetEventA在此处被释放
Console.WriteLine("TaskB done");
Thread.Sleep(5000);
});
Parallel.Invoke(()=>taskA.Start(),()=>taskB.Start());
taskA.Wait();
taskB.Wait();