我有一个class
,提供对LinkedList<>
的线程安全访问(添加和阅读项目)。
class LinkedListManager {
public static object locker = new object();
public static LinkedList<AddXmlNodeArgs> tasks { get; set; }
public static EventWaitHandle wh { get; set; }
public void AddItemThreadSafe(AddXmlNodeArgs task) {
lock (locker)
tasks.AddLast(task);
wh.Set();
}
public LinkedListNode<AddXmlNodeArgs> GetNextItemThreadSafe(LinkedListNode<AddXmlNodeArgs> prevItem) {
LinkedListNode<AddXmlNodeArgs> nextItem;
if (prevItem == null) {
lock (locker)
return tasks.First;
}
lock (locker) // *1
nextItem = prevItem.Next;
if (nextItem == null) { // *2
wh.WaitOne();
return prevItem.Next;
}
lock (locker)
return nextItem;
}
}
}
我有3个主题:第1个 - 将数据写入tasks
;第二和第三 - 从tasks
读取数据。
在第2和第3个主题中,我通过调用tasks
从GetNextItemThreadSafe()
检索数据。
问题,当方法(GetNextItemThreadSafe()
)的参数不为空时,有时null
会返回prevItem
。
问题:
某个帖子可以某种方式跳过lock(locker)
(// *1
)并立即转到// *2
吗?
我认为这是从null
获得返回值= GetNextItemThreadSafe()
的唯一方法...
我花了一整天才发现错误,但这非常困难,因为它似乎几乎不可能一步一步地调试它(tasks
包含5.000个元素并且发生错误无论何时它想要)。顺便说一下,有时程序运行正常 - 毫无例外。
我是线程新手,所以也许我会问愚蠢的问题......
答案 0 :(得分:0)
lock
仅在执行锁定后声明的代码块时才有效。由于您在单个命令上多次锁定,因此这有效地退化为仅锁定lock
之后的单个命令,之后另一个线程可以自由跳入并使用数据。也许你的意思是:
public LinkedListNode<AddXmlNodeArgs> GetNextItemThreadSafe(LinkedListNode<AddXmlNodeArgs> prevItem) {
LinkedListNode<AddXmlNodeArgs> nextItem;
LinkedListNode<AddXmlNodeArgs> returnItem;
lock(locker) { // Lock the entire method contents to make it atomic
if (prevItem == null) {
returnItem = tasks.First;
}
// *1
nextItem = prevItem.Next;
if (nextItem == null) { // *2
// wh.WaitOne(); // Waiting in a locked block is not a good idea
returnItem = prevItem.Next;
}
returnItem = nextItem;
}
return returnItem;
}
}
请注意,在锁定的块中只发生分配(而不是返回),并且在方法的底部有一个返回点。
答案 1 :(得分:0)
不清楚你想要实现的目标。这两个线程是否应该获得链表的相同元素?或者您是否尝试让2个线程并行处理列表中的任务?如果这是第二种情况,那么你正在做的事情是行不通的。你最好看看BlockingCollection,它是线程安全的,专为这种多线程生产者/消费者模式而设计。
答案 2 :(得分:0)
我认为解决方案如下:
在您的添加方法中,添加节点并将EventWaitHandle 设置为在同一锁内
在Get方法中,在锁定内部,检查同一个锁中的下一个元素是否为空和,重置EventWaitHandle。在锁定之外,等待EventWaitHandle。