数据结构(逻辑结构,物理结构,特点)C#多线程编程的同步也线程安全C#多线程编程笔记Str。。。
⼀、数据的 :指反映数据 之间的逻辑关系的 ,其中的 是指数据元素之间的前后件关系,⽽与他们在计算机中的存储位置⽆关。逻辑结构包括:
1. 集合
数据结构中的元素之间除了“同属⼀个集合” 的相互关系外,别⽆其他关系;
2.
数据结构中的元素存在⼀对⼀的相互关系;
3.
数据结构中的元素存在⼀对多的相互关系;
4.
数据结构中的元素存在多对多的相互关系。
⼆、数据的物理结构:指数据的 在计算机存储空间的存放形式。
数据的物理结构是数据结构在计算机中的表⽰(⼜称映像),它包括数据元素的机内表⽰和关系的机内表⽰。由于具体实现的⽅法有顺序、链接、索引、散列等多种,所以,⼀种数据结构可表⽰成⼀种或多种存储结构。
数据元素的机内表⽰(映像⽅法): ⽤⼆进制位(bit)的位串表⽰数据元素。通常称这种位串为节点(node)。当数据元素有若⼲个数据项组成时,位串中与个数据项对应的⼦位串称为数据域(data field)。因此,节点是数据元素的机内表⽰(或机内映像)。
关系的机内表⽰(映像⽅法):数据元素之间的关系的机内表⽰可以分为顺序映像和⾮顺序映像,常⽤两种存储结构:顺序存储结构和链式存储结构。顺序映像借助元素在存储器中的相对位置来表⽰数据元素之间的逻辑关系。⾮顺序映像借助指⽰元素存储位置的指针(pointer)来表⽰数据元素之间的逻辑关系。
数组
在程序设计中,为了处理⽅便, 把具有相同类型的若⼲ 按有序的形式组织起来。这些按序排列的同类数据元素的集合称为 。在 中, 数组属于构造数据类型。⼀个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组⼜可分为数值数组、字符数组、 、结构数组等各种类别。
栈
是只能在某⼀端插⼊和删除的特殊 。它按照先进后出的原则存储数据,先进⼊的数据被压⼊栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后⼀个数据被第⼀个读出来)。
队列
⼀种特殊的 ,它只允许在表的 (front)进⾏删除操作,⽽在表的后端(rear)进⾏插⼊操作。进⾏插⼊操作的端称为队尾,进⾏删除操作的端称为队头。队列是按照“先进先出”或“后进后出”的原则组织数据的。队列中没有元素时,称为空队列。
链表
是⼀种物理存储单元上⾮连续、⾮顺序的存储结构,它既可以表⽰线性结构,也可以⽤于表⽰⾮线性结构,数据元素的 是通过链表中的指针链接次序实现的。链表由⼀系列结点(链表中每⼀个元素称为结点)组成,结点可以在运⾏时 ⽣成。每个结点包括两个部分:⼀个是存储数据元素的数据域,另⼀个是存储下⼀个结点地址的指针域。
树
是包含n(n>0)个结点的有穷集合K,且在K中定义了⼀个关系N,N满⾜ 以下条件:
(1)有且仅有⼀个结点 K0,他对于关系N来说没有前驱,称K0为树的根结点。简称为根( )。 (2)除K0外,K中的每个结点,对于关系N来说有且仅有⼀个前驱。
(3)K中各结点,对关系N来说可以有m个后继(m>=0)。
顶点之间存在⼀条边,就表⽰这两个顶点具有相邻关系。
堆
在计算机科学中,堆是⼀种特殊的树形数据结构,每个结点都有⼀个值。通常我们所说的堆的数据结构,是指⼆叉堆。堆的特点是根结点的值最⼩(或最⼤),且根结点的两个⼦树也是⼀个堆。
散列表
若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需⽐较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建⽴的表为 。
前⼀篇⽂章记录了简单的多线程编程的⼏种⽅式,但是在实际的项⽬中,也需要等待多线程执⾏完成之后再执⾏的⽅法,这个就叫做多线程的同步,或者,由于多个线程对同⼀对象的同时操作造成数据错乱,需要线程安全。这篇⽂章主要记录多线程的同步异步如何实现线程安全的⼏种⽅式的笔记,如有错误,请⼤神不吝赐教。
因为代码⾥⾯有很详细的注释,所以下⾯直接附上代码,不做过多的解释,如有疑问可以百度相关主题的⽂章详细了解。
1、 Mutex
1.Mutex测试
Mutex互斥锁,⽤于多线程间的线程同步通过WaitOne等待当前锁定的线程执⾏完成,例如,线程B执⾏需要等待线程A执⾏结束的情况下,可以使⽤Mutex
同时Mutex还有⼀个⽐较有趣的功能就是可以设置实现客户端在同⼀太电脑上只能打开⼀个进程
//bool createNew = false;
//Mutex mutex = new Mutex(true, "MutexTest", out createNew);
//AutoResetEvent ae = new AutoResetEvent(false);//定义⼀个信号量,表⽰执⾏结束,可以释放互斥锁
参数1表⽰初始化的时候当前互斥锁是否已被获取,false代表未被获取,
参数2标识当前互斥锁的名称,指定⼀个名称,配合参数3即可实现只能开启⼀个进程的效果
参数3表⽰是否创建了⼀个新的互斥锁
//Thread t1 = new Thread(new ThreadStart(() =>
//{
// Console.WriteLine("我是线程1");
// Console.WriteLine("线程1开始执⾏!");
// Thread.Sleep(1000);//线程休眠1秒钟,⽤于模拟需要较长时间执⾏的功能
// Console.WriteLine("线程1执⾏结束!");
// ae.Set();
//}));
//Thread t2 = new Thread(() =>
// Console.WriteLine("线程2开始执⾏!");
/
/ mutex.WaitOne();//等待互斥锁被释放,模拟实际项⽬中需要其他线程执⾏完毕⽅可执⾏的功能
// Console.WriteLine("线程2执⾏结束!");
// });
因为是多线程执⾏,所以线程1与线程2的谁先开始执⾏,以上代码中未进⾏控制,
但线程2⼀定是在线程1执⾏完成之后才能结束
//t1.Start();
//t2.Start();
//ae.WaitOne();//等待释放信息
//mutex.ReleaseMutex();//释放互斥锁
AutoResetEvent的功能类似于⼀个红绿灯信号,当达到可以释放的条件的时候,调⽤Set⽅法来通知后续代码可以执⾏了,
此处为何需要⼀个信号,是因为Mutex定义在主线程中,如果在异步线程中释放,会报⼀个错,提⽰在不安全的代码块中执⾏ 互斥锁,所以此处使⽤信号来通知主线程可以释放互斥锁了
2、AutoResetEvent
/// <summary>
/// 通过AutoRestEvent实现线程同步
/// </summary>
public void TestAutoResetEvent()
{
AutoResetEvent[] autoResetEvents = new AutoResetEvent[3];
autoResetEvents[0] = new AutoResetEvent(false);//定义初始信号为关
autoResetEvents[1] = new AutoResetEvent(false);
autoResetEvents[2] = new AutoResetEvent(false);
//以下代码实现线程1结束之后线程2才能结束,线程2结束之后线程3才能开始,所有线程都结束之后主线程才能继续
Thread t1 = new Thread(new ThreadStart(() =>
{
Console.WriteLine("线程1开始!");
Thread.Sleep(1000);
Console.WriteLine("线程1结束!");
autoResetEvents[0].Set();
}));
Thread t2 = new Thread(new ThreadStart(() =>
Thread.Sleep(1000);
autoResetEvents[0].WaitOne();
Console.WriteLine("线程2结束!");
autoResetEvents[1].Set();
}));
Thread t3 = new Thread(new ThreadStart(() =>
{
autoResetEvents[1].WaitOne();
Console.WriteLine("线程3开始!");
Thread.Sleep(1000);
Console.WriteLine("线程3结束!");
autoResetEvents[2].Set();
}));
t1.Start();
t2.Start();
t3.Start();
Console.WriteLine("主线程开始等待......");
autoResetEvents[2].WaitOne();//等待所有线程结束
//AutoResetEvent从字⾯即可知道是⾃动信号,意思为当信号被捕捉之后会⾃动重置为关闭状态
//对应的ManualResetEvent为⼿动信号,使⽤⽅法相同但是在被捕捉之后不会被重置为关闭状态
//需要⼿动调⽤Reset⽅法关闭信号,如果是简单的同步,使⽤⾃动信号即可,如果需要很复杂的流程控制
//可以使⽤⾃动信号,同时可以配合WaitHandle来实现线程的同步,WaitHandle拥有WaitAny⽅法等待任意⼀个信号 //WaitAll⽅法等待所有信号,使⽤⽅法与信号的WaiOne相似,此处不再进⾏举例,可以查看相关⽂章具体了解
Console.WriteLine("主线程执⾏结束!");
}
3、 lock与Monitor
/// <summary>
/// 测试lock和Monitor实现线程安全的多线程
/// </summary>
public void TestLockAndMonitor()
{
//lock与monitor实现相同的功能,多线程的线程安全
int count = 1;
int sum = 0;
for (int i = 0; i < 20; i++)
{
Thread t = new Thread(new ThreadStart(() =>
{
for (int j = 0; j < 1000; j++)
{
//此处保证线程安全的原理是,当前多个线程同时访问count的时候,如果不lock
//可能多个线程访问到的count是相同的值,这样虽然多个线程都执⾏了count++但是
什么是编程举个例子//结果却没有加上去,造成最终的结果错误,当lock之后,lock内部的代码每次只能
//有⼀个线程访问,所以每个线程获取的count都不可能相同,这样就能保证最后的结果⼀定是正确的 //lock (obj)//取消此句代码测试多线程的不安全性,取消之后可能每次执⾏的结果都不⼀样
//{
// sum += count;
// count++;
//}
//使⽤下⾯的⽅法与使⽤lock的功能相同
//Monitor.Enter(obj);
//sum += count;
//count++;
//Monitor.Exit(obj);
}
}));
t.Start();
}
Thread.Sleep(3000);//延时3秒保证异步线程全部执⾏完成
Console.WriteLine(sum);
}
4、信号量
/// <summary>
/// 测试信号量实现线程安全
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论