什么是线程安全?

答:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

前面几篇写的线性结构,在多线程并行的情况下会出现共享数据会线程间读取与写入不一直的情况,为了解决这种情况,通常会使用锁来解决,也就是将并行改为串行。但是在使用穿行违背了使用多线程并发的初衷,这种情况下我们可以考虑采用线程安全结构。

先看下线程安全队列的用法:

ConcurrentQueue<int> ts = new  System.Collections.Concurrent.ConcurrentQueue<int>();
ts.Enqueue(1);
ts.Enqueue(2);
ts.Enqueue(3);
ts.Enqueue(4);
foreach (var r in ts)
{
Console.Write($"data:{r} ");
}
Console.WriteLine();
ts.TryPeek(out int pk);
Console.WriteLine($"peek:{pk}");
ts.TryDequeue(out int ck);
ts.Enqueue(5);
ts.Enqueue(6);
Console.WriteLine();
foreach (var r in ts)
{
Console.Write($"data:{r} ");
}
Console.WriteLine();
Console.ReadLine();

现在我们看下线程安全队列的实现方式:(参考自:.net framework 4.8),核心代码全部做了注释。

总的来说,(总结语放到前面,防止代码篇幅太大,同志们没有耐心翻到最底下~)

1、线程安全队列通过SpinWait自旋类来实现等待并行线程完成与Interlocked原子操作类计数实现的。

2、线程安全队列通过单向链表实现的,链的节点为长度32的数组,通过记录链的头节点与尾节点、以及队列的头尾实现队列的存储与入队、出队操作的。

public class MyConcurrentQueue<T> : IProducerConsumerCollection<T>
{
[NonSerialized]
private volatile Segment m_head; [NonSerialized]
private volatile Segment m_tail; private T[] m_serializationArray; private const int SEGMENT_SIZE = 32; [NonSerialized]
internal volatile int m_numSnapshotTakers = 0;
/// <summary>
/// 链尾部节点
/// </summary>
public MyConcurrentQueue()
{
m_head = m_tail = new Segment(0, this);
}
//尝试添加
bool IProducerConsumerCollection<T>.TryAdd(T item)
{
Enqueue(item);
return true;
}
/// <summary>
/// 尝试从中移除并返回对象
/// </summary>
/// <param name="item">
/// </remarks>
bool IProducerConsumerCollection<T>.TryTake(out T item)
{
return TryDequeue(out item);
}
/// <summary>
/// 判断当前链是否为空
/// </summary>
public bool IsEmpty
{
get
{
Segment head = m_head;
if (!head.IsEmpty)
//如果头不为空,则链非空
return false;
else if (head.Next == null)
//如果头节点的下一个节点为空,且为链尾,
return true;
else
//如果头节点为空且不是最后一个节点 ,则标识另一个线程正在写入该数组
//等待中..
{
SpinWait spin = new SpinWait();
while (head.IsEmpty)
{
//此时为空
if (head.Next == null)
return true;
//否则标识正在有线程占用写入
//线程循环一次
spin.SpinOnce();
head = m_head;
}
return false;
}
}
}
/// <summary>
/// 用来判断链是否在变化
/// </summary>
/// <param name="head"></param>
/// <param name="tail"></param>
/// <param name="headLow"></param>
/// <param name="tailHigh"></param>
private void GetHeadTailPositions(out Segment head, out Segment tail,
out int headLow, out int tailHigh)
{
head = m_head;
tail = m_tail;
headLow = head.Low;
tailHigh = tail.High;
SpinWait spin = new SpinWait();
Console.WriteLine($"head.Low:{head.Low},tail.High:{tail.High},head.m_index:{head.m_index},tail.m_index:{tail.m_index}");
//通过循环来保证值不再更改(也就是说并行线程操作结束)
//保证线程串行核心的判断逻辑
while (
//头尾发生变化
head != m_head || tail != m_tail
//如果队列头、尾索引发生变化
|| headLow != head.Low || tailHigh != tail.High
|| head.m_index > tail.m_index)
{
spin.SpinOnce();
head = m_head;
tail = m_tail;
headLow = head.Low;
tailHigh = tail.High;
}
}
/// <summary>
/// 获取总数
/// </summary>
public int Count
{
get
{
Segment head, tail;
int headLow, tailHigh;
GetHeadTailPositions(out head, out tail, out headLow, out tailHigh);
if (head == tail)
{
return tailHigh - headLow + 1;
}
//头节点长度
int count = SEGMENT_SIZE - headLow;
//加上中间其他节点长度
count += SEGMENT_SIZE * ((int)(tail.m_index - head.m_index - 1));
//加上尾节点长度
count += tailHigh + 1;
return count;
}
} public object SyncRoot => throw new NotImplementedException(); public bool IsSynchronized => throw new NotImplementedException(); public void CopyTo(T[] array, int index)
{ }
/// <summary>
/// 暂未实现
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return null;
}
/// <summary>
/// 添加
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
{
SpinWait spin = new SpinWait();
while (true)
{
Segment tail = m_tail;
if (tail.TryAppend(item))
return;
spin.SpinOnce();
}
}
/// <summary>
/// 尝试删除节点
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public bool TryDequeue(out T result)
{
while (!IsEmpty)
{
Segment head = m_head;
if (head.TryRemove(out result))
return true;
}
result = default(T);
return false;
}
/// <summary>
/// 查看最后一个添加入的元素
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public bool TryPeek(out T result)
{
//原子增加值
Interlocked.Increment(ref m_numSnapshotTakers); while (!IsEmpty)
{
//首先从头节点看一下第一个节点是否存在
Segment head = m_head;
if (head.TryPeek(out result))
{
Interlocked.Decrement(ref m_numSnapshotTakers);
return true;
}
}
result = default(T);
Interlocked.Decrement(ref m_numSnapshotTakers);
return false;
} public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
} IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
} public T[] ToArray()
{
throw new NotImplementedException();
}
/// <summary>
/// 为线程安全队列提供一个 单向链表,
/// 链表的每个节点存储长度为32的数组
/// </summary>
private class Segment
{
/// <summary>
/// 定义一个数组,用于存储每个节点的内容
/// </summary>
internal volatile T[] m_array;
/// <summary>
/// 定义一个结构数组,用于标识数组中每个节点是否有效(是否存储内容)
/// </summary>
internal volatile VolatileBool[] m_state;
//指针,指向下一个节点数组
//如果是最后一个节点,则节点为空
private volatile Segment m_next;
/// <summary>
/// 索引,用来存储链表的长度
/// </summary>
internal readonly long m_index;
/// <summary>
/// 用来标识队列头-数组弹出索引
/// </summary>
private volatile int m_low;
/// <summary>
/// 用来标识队列尾-数组最新存储位置
/// </summary>
private volatile int m_high;
/// <summary>
/// 用来标识队列
/// </summary>
private volatile MyConcurrentQueue<T> m_source;
/// <summary>
/// 实例化链节点
/// </summary>
internal Segment(long index, MyConcurrentQueue<T> source)
{
m_array = new T[SEGMENT_SIZE];
m_state = new VolatileBool[SEGMENT_SIZE]; //all initialized to false
m_high = -1;
m_index = index;
m_source = source;
}
/// <summary>
/// 链表的下一个节点
/// </summary>
internal Segment Next
{
get { return m_next; }
}
/// <summary>
/// 如果当前节点数组为空返回true,
/// </summary>
internal bool IsEmpty
{
get { return (Low > High); }
}
/// <summary>
/// 非安全添加方法(无判断数组长度)
/// </summary>
/// <param name="value"></param>
internal void UnsafeAdd(T value)
{
m_high++;
m_array[m_high] = value;
m_state[m_high].m_value = true;
} internal Segment UnsafeGrow()
{
Segment newSegment = new Segment(m_index + 1, m_source);
m_next = newSegment;
return newSegment;
} /// <summary>
/// 如果当前数组满了 >=32,则链扩展节点。
/// </summary>
internal void Grow()
{
//重新船舰数组
Segment newSegment = new Segment(m_index + 1, m_source);
//赋值给next指针
m_next = newSegment;
//将节点添加到链
m_source.m_tail = m_next;
} /// <summary>
/// 在末尾添加元素
/// </summary>
/// <param name="value">元素</param>
/// <param name="tail">The tail.</param>
/// <returns>如果附加元素,则为true;如果当前数组已满,则为false</returns>
/// <remarks>如果附加指定的元素成功,并且在此之后数组满了,在链上添加新节点(节点为32长度数组) </remarks>
internal bool TryAppend(T value)
{
//如果数组已满则跳出方法
if (m_high >= SEGMENT_SIZE - 1)
{
return false;
}
//局部变量初始化
int newhigh = SEGMENT_SIZE;
try
{ }
finally
{
//原子递增
newhigh = Interlocked.Increment(ref m_high);
if (newhigh <= SEGMENT_SIZE - 1)
{
m_array[newhigh] = value;
m_state[newhigh].m_value = true;
}
//如果数组满了,则扩展链节点。
if (newhigh == SEGMENT_SIZE - 1)
{
Grow();
}
}
//如果 newhigh <= SEGMENT_SIZE-1, 这意味着当前线程成功地占据了一个位置
return newhigh <= SEGMENT_SIZE - 1;
} /// <summary>
/// 尝试从链的头部数组删除节点
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
internal bool TryRemove(out T result)
{
SpinWait spin = new SpinWait();
int lowLocal = Low, highLocal = High;
while (lowLocal <= highLocal)
{
//获取队头索引
if (Interlocked.CompareExchange(ref m_low, lowLocal + 1, lowLocal) == lowLocal)
{
//如果要弹出队列的值不可用,说明这个位置被并行线程获取到了权限,但是值还未写入。
//通过线程自旋等待值写入
SpinWait spinLocal = new SpinWait();
while (!m_state[lowLocal].m_value)
{
spinLocal.SpinOnce();
}
//取出值
result = m_array[lowLocal];
// 如果没有其他线程读取(GetEnumerator()、ToList()) 执行删除
// 如 TryPeek 的时候m_numSnapshotTakers会在进入方法体时++,在出方法体--
// 清空该索引下的值
if (m_source.m_numSnapshotTakers <= 0)
m_array[lowLocal] = default(T);
//如果说lowLocal+1 = 32 说明当前链节点的数组已经全部出队
if (lowLocal + 1 >= SEGMENT_SIZE)
{
//由于lowLocal <= highLocal成立
//lowLocal + 1 >= SEGMENT_SIZE 如果成立 ,且m_next == null 成立,
//说明在此时有其他线程正在做扩展链结构
//那么当前线程需要等待其他线程完成扩展链表,再做出队操作。
spinLocal = new SpinWait();
while (m_next == null)
{
spinLocal.SpinOnce();
}
m_source.m_head = m_next;
}
return true;
}
else
{
//此时说明 当前线程竞争资源失败,做短暂自旋后继续竞争资源
spin.SpinOnce();
lowLocal = Low; highLocal = High;
}
}
//失败的情况下返回空值
result = default(T);
return false;
}
/// <summary>
/// 尝试获取队列头节点元素
/// </summary>
internal bool TryPeek(out T result)
{
result = default(T);
int lowLocal = Low;
//校验当前队列是否正确
if (lowLocal > High)
return false;
SpinWait spin = new SpinWait();
//如果头节点无效,则说明当前节点被其他线程占用,并在做写入操作,
//需要等待其他线程写入后再执行读取操作
while (!m_state[lowLocal].m_value)
{
spin.SpinOnce();
}
result = m_array[lowLocal];
return true;
}
/// <summary>
/// 返回队列首位置
/// </summary>
internal int Low
{
get
{
return Math.Min(m_low, SEGMENT_SIZE);
}
}
/// <summary>
/// 获取队列长度
/// </summary>
internal int High
{
get
{
//如果m_high>SEGMENT_SIZE,则表示超出范围,我们应该返回 SEGMENT_SIZE-1
return Math.Min(m_high, SEGMENT_SIZE - 1);
}
}
}
}
/// <summary>
/// 结构-用来存储整数组每个索引上是否存储值
/// </summary>
struct VolatileBool
{
public VolatileBool(bool value)
{
m_value = value;
}
public volatile bool m_value;
}

代码通篇看下来有些长(已经精简了很多,只实现入队、出队、与查看下一个出队的值),不知道有多少人能翻到这里~

说明:

1、TryAppend方法通过Interlocked.Increment()原子递增方法获取下一个数组存储点,通过比对32判断链是否需要增加下一个链节点,也就是说,链的存储空间每次扩展为32个存储位置。

2、TryRemove方法通过 Interlocked.CompareExchange()方法来判断当前是否有并行线程在写入,如果有则通过 while循环 SpinWait类的SpinOnce()方法实现等待写入完成后,再做删除;特别说明,判断是否写入是靠VolatileBool结构来实现的,每个链表的每个节点在存储值的同时每个存储都对应一个VolatileBool结构用来标识当前写入点是否成功写入。特殊情况,如果当前链节点的数组已经空了,则需要pinWait类的SpinOnce()简短的自旋等待并行的写入方法完成扩展链后,再做删除。

3、TryPeek方法,同样会判断要获取的元素是否已经成功写入(不成功则说明并行线程还未完成写入),如果未完成,则通过 while pinWait类的SpinOnce()来等待写入完成后,再读取元素内容。

现在代码已经看完了,来试下:

MyConcurrentQueue<string> myConcurrentQueue = new MyConcurrentQueue<string>();
for (int i = 0; i < 67; i++)
{
myConcurrentQueue.Enqueue($"第{i}位");
Console.WriteLine($"总数:{myConcurrentQueue.Count}");
} myConcurrentQueue.TryPeek(out string rs);
Console.WriteLine($"TryPeek 总数:{myConcurrentQueue.Count}");
for (int i = 0; i < 34; i++)
{
myConcurrentQueue.TryDequeue(out string result0);
Console.WriteLine($"TryDequeue 总数:{myConcurrentQueue.Count}");
}
Console.ReadKey();

打印:

head.Low:0,tail.High:0,head.m_index:0,tail.m_index:0
总数:1
head.Low:0,tail.High:1,head.m_index:0,tail.m_index:0
总数:2
head.Low:0,tail.High:2,head.m_index:0,tail.m_index:0
总数:3
head.Low:0,tail.High:3,head.m_index:0,tail.m_index:0
总数:4
head.Low:0,tail.High:4,head.m_index:0,tail.m_index:0
总数:5
head.Low:0,tail.High:5,head.m_index:0,tail.m_index:0
总数:6
head.Low:0,tail.High:6,head.m_index:0,tail.m_index:0
总数:7
head.Low:0,tail.High:7,head.m_index:0,tail.m_index:0
总数:8
head.Low:0,tail.High:8,head.m_index:0,tail.m_index:0
总数:9
head.Low:0,tail.High:9,head.m_index:0,tail.m_index:0
总数:10
head.Low:0,tail.High:10,head.m_index:0,tail.m_index:0
总数:11
head.Low:0,tail.High:11,head.m_index:0,tail.m_index:0
总数:12
head.Low:0,tail.High:12,head.m_index:0,tail.m_index:0
总数:13
head.Low:0,tail.High:13,head.m_index:0,tail.m_index:0
总数:14
head.Low:0,tail.High:14,head.m_index:0,tail.m_index:0
总数:15
head.Low:0,tail.High:15,head.m_index:0,tail.m_index:0
总数:16
head.Low:0,tail.High:16,head.m_index:0,tail.m_index:0
总数:17
head.Low:0,tail.High:17,head.m_index:0,tail.m_index:0
总数:18
head.Low:0,tail.High:18,head.m_index:0,tail.m_index:0
总数:19
head.Low:0,tail.High:19,head.m_index:0,tail.m_index:0
总数:20
head.Low:0,tail.High:20,head.m_index:0,tail.m_index:0
总数:21
head.Low:0,tail.High:21,head.m_index:0,tail.m_index:0
总数:22
head.Low:0,tail.High:22,head.m_index:0,tail.m_index:0
总数:23
head.Low:0,tail.High:23,head.m_index:0,tail.m_index:0
总数:24
head.Low:0,tail.High:24,head.m_index:0,tail.m_index:0
总数:25
head.Low:0,tail.High:25,head.m_index:0,tail.m_index:0
总数:26
head.Low:0,tail.High:26,head.m_index:0,tail.m_index:0
总数:27
head.Low:0,tail.High:27,head.m_index:0,tail.m_index:0
总数:28
head.Low:0,tail.High:28,head.m_index:0,tail.m_index:0
总数:29
head.Low:0,tail.High:29,head.m_index:0,tail.m_index:0
总数:30
head.Low:0,tail.High:30,head.m_index:0,tail.m_index:0
总数:31
head.Low:0,tail.High:-1,head.m_index:0,tail.m_index:1
总数:32
head.Low:0,tail.High:0,head.m_index:0,tail.m_index:1
总数:33
head.Low:0,tail.High:1,head.m_index:0,tail.m_index:1
总数:34
head.Low:0,tail.High:2,head.m_index:0,tail.m_index:1
总数:35
head.Low:0,tail.High:3,head.m_index:0,tail.m_index:1
总数:36
head.Low:0,tail.High:4,head.m_index:0,tail.m_index:1
总数:37
head.Low:0,tail.High:5,head.m_index:0,tail.m_index:1
总数:38
head.Low:0,tail.High:6,head.m_index:0,tail.m_index:1
总数:39
head.Low:0,tail.High:7,head.m_index:0,tail.m_index:1
总数:40
head.Low:0,tail.High:8,head.m_index:0,tail.m_index:1
总数:41
head.Low:0,tail.High:9,head.m_index:0,tail.m_index:1
总数:42
head.Low:0,tail.High:10,head.m_index:0,tail.m_index:1
总数:43
head.Low:0,tail.High:11,head.m_index:0,tail.m_index:1
总数:44
head.Low:0,tail.High:12,head.m_index:0,tail.m_index:1
总数:45
head.Low:0,tail.High:13,head.m_index:0,tail.m_index:1
总数:46
head.Low:0,tail.High:14,head.m_index:0,tail.m_index:1
总数:47
head.Low:0,tail.High:15,head.m_index:0,tail.m_index:1
总数:48
head.Low:0,tail.High:16,head.m_index:0,tail.m_index:1
总数:49
head.Low:0,tail.High:17,head.m_index:0,tail.m_index:1
总数:50
head.Low:0,tail.High:18,head.m_index:0,tail.m_index:1
总数:51
head.Low:0,tail.High:19,head.m_index:0,tail.m_index:1
总数:52
head.Low:0,tail.High:20,head.m_index:0,tail.m_index:1
总数:53
head.Low:0,tail.High:21,head.m_index:0,tail.m_index:1
总数:54
head.Low:0,tail.High:22,head.m_index:0,tail.m_index:1
总数:55
head.Low:0,tail.High:23,head.m_index:0,tail.m_index:1
总数:56
head.Low:0,tail.High:24,head.m_index:0,tail.m_index:1
总数:57
head.Low:0,tail.High:25,head.m_index:0,tail.m_index:1
总数:58
head.Low:0,tail.High:26,head.m_index:0,tail.m_index:1
总数:59
head.Low:0,tail.High:27,head.m_index:0,tail.m_index:1
总数:60
head.Low:0,tail.High:28,head.m_index:0,tail.m_index:1
总数:61
head.Low:0,tail.High:29,head.m_index:0,tail.m_index:1
总数:62
head.Low:0,tail.High:30,head.m_index:0,tail.m_index:1
总数:63
head.Low:0,tail.High:-1,head.m_index:0,tail.m_index:2
总数:64
head.Low:0,tail.High:0,head.m_index:0,tail.m_index:2
总数:65
head.Low:0,tail.High:1,head.m_index:0,tail.m_index:2
总数:66
head.Low:0,tail.High:2,head.m_index:0,tail.m_index:2
总数:67
head.Low:0,tail.High:2,head.m_index:0,tail.m_index:2
TryPeek 总数:67
head.Low:1,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:66
head.Low:2,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:65
head.Low:3,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:64
head.Low:4,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:63
head.Low:5,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:62
head.Low:6,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:61
head.Low:7,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:60
head.Low:8,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:59
head.Low:9,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:58
head.Low:10,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:57
head.Low:11,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:56
head.Low:12,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:55
head.Low:13,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:54
head.Low:14,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:53
head.Low:15,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:52
head.Low:16,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:51
head.Low:17,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:50
head.Low:18,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:49
head.Low:19,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:48
head.Low:20,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:47
head.Low:21,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:46
head.Low:22,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:45
head.Low:23,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:44
head.Low:24,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:43
head.Low:25,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:42
head.Low:26,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:41
head.Low:27,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:40
head.Low:28,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:39
head.Low:29,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:38
head.Low:30,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:37
head.Low:31,tail.High:2,head.m_index:0,tail.m_index:2
TryDequeue 总数:36
head.Low:0,tail.High:2,head.m_index:1,tail.m_index:2
TryDequeue 总数:35
head.Low:1,tail.High:2,head.m_index:1,tail.m_index:2
TryDequeue 总数:34
head.Low:2,tail.High:2,head.m_index:1,tail.m_index:2
TryDequeue 总数:33

有时间希望大家能将代码跑一下,相信会更明白其中的原理。

C#数据结构-线程安全队列的更多相关文章

  1. 线程池 队列 synchronized

    线程池 BlockingQueue synchronized volatile 本章从线程池到阻塞队列BlockingQueue.从BlockingQueue到synchronized 和 volat ...

  2. JS数据结构与算法-队列结构

    队列结构 一.认识队列 受限的线性结构: 我们已经学习了一种受限的线性结构:栈结构. 并且已经知道这种受限的数据结构对于解决某些特定问题,会有特别的 效果. 下面,我们再来学习另外一个受限的数据结构: ...

  3. javascript数据结构与算法---队列

    javascript数据结构与算法---队列 队列是一种列表,不同的是队列只能在队尾插入元素,在队首删除元素.队列用于存储按顺序排列的数据,先进先出,这点和栈不一样(后入先出).在栈中,最后入栈的元素 ...

  4. Linux多线程系列-2-条件变量的使用(线程安全队列的实现)

    多线程情况下,往往需要使用互斥变量来实现线程间的同步,实现资源正确共享. linux下使用如下变量和函数 //条件变量 pthread_cond_t int pthread_cond_init (pt ...

  5. 使用Condition Variables 实现一个线程安全队列

    使用Condition Variables实现一个线程安全队列 测试机: i7-4800MQ .7GHz, logical core, physical core, 8G memory, 256GB ...

  6. [数据结构与算法]队列Queue 的多种实现

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  7. C++数据结构之链式队列(Linked Queue)

    C++数据结构之链式队列,实现的基本思想和链式栈的实现差不多,比较不同的一点也是需要注意的一点是,链式队列的指向指针有两个,一个是队头指针(front),一个是队尾指针(rear),注意指针的指向是从 ...

  8. [数据结构]C语言队列的实现

    我个人把链表.队列.栈分为一类,然后图.树分为一类.(串不考虑),分类的理由就是每一类有规律可循,即你能通过修改极少数的代码把链表变成队列.栈.(这里我们不考虑其他诸如设计模式等因素),因此本贴在讲完 ...

  9. JavaScript数据结构和算法----队列

    前言 队列和栈很像,只是用了不同的原则.队列是遵循先进先出(FIFO)原则的一组有序的的项,队列在尾部添加新元素,从顶部移除元素.最新添加的元素必须必须排队在队列的,末尾.可以想象食堂排队买饭的样子. ...

随机推荐

  1. java内存屏障

    为什么会有内存屏障 每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取.但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的 ...

  2. CentOS7的下载及虚拟机的创建

    一.CentOS的安装 1,首先打开开源镜像网站:www.mirrors.163.com(网易开源镜像网站),www.mirrors.aliyun.com(阿里云开源镜像网站) 以网易为例 2.点击进 ...

  3. IDEA 使用的配置

    IDEA 使用 工欲善其事必先利其器,选择适合自己的 IDE,会让自己事倍功半.作为 Java 开发环境,有人喜欢 Eclipse,有人喜欢 idea,这其中的差别对比这里不做对比,需要了解的朋友可以 ...

  4. 077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象

    077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象 本文知识点:类和对象 说明:因为时间紧张,本人写博客过程中只是对知识点 ...

  5. matlab中imfilter的用法

    来源:https://blog.csdn.net/qq_15971883/article/details/78334380 转载自:http://blog.csdn.net/u013066730/ar ...

  6. 南方IT比赛项目

    注意: 出现以下提示点击否就可以了 导入工作台: 导入模型 安装 安装到工作台上 修改模型位置 更新工具位置 点击是 改角度 九十度 添加组件 改一下名字,方便记忆 把工具移到组件 拆除后将工具移到S ...

  7. ConcurrentHashMap原理分析(二)-扩容

    概述 在上一篇文章中介绍了ConcurrentHashMap的存储结构,以及put和get方法,那本篇文章就介绍一下其扩容原理.其实说到扩容,无非就是新建一个数组,然后把旧的数组中的数据拷贝到新的数组 ...

  8. 深入理解Logger日志——框架绑定原理

    深入理解Logger日志--框架绑定原理 说到Logger日志的动态绑定,主要归功与Slf4j,在之前的文章也说过,Slf4j是类似于Apache Common-Logging,英文为Simple L ...

  9. php中 ob_函数 例:ob_start();用法

    ob,输出缓冲区,是output buffering的简称,而不是output cache.ob用对了,是能对速度有一定的帮助,但是盲目的加上ob函数,只会增加CPU额外的负担 ob的基本原则:如果o ...

  10. org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, "enterpCd")-Mybatis报错

    一.问题由来 下午快要下班时,登录测试服务器查看日志信息,看看有没有新的异常信息,如果有的话好及时修改.结果一看果然有新的异常信息. 主要的异常信息如下: 2020-10-13 14:51:03,03 ...