Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何实现对列表的并发线程安全访问?_C#_Multithreading - Fatal编程技术网

C# 如何实现对列表的并发线程安全访问?

C# 如何实现对列表的并发线程安全访问?,c#,multithreading,C#,Multithreading,如果锁内有一个安全的对象引用,而锁外没有同步就开始使用它,那该怎么办?解除锁定后,其他线程无法使用此对象。是否可以保证处理器中没有过时或缓存的数据?在得到锁之前,我会看到其他线程所做的所有更改吗?一般来说,它是线程安全的吗 让我展示一些代码 private List<string> list1 = new List<string>(); private List<string> list2 = new List<string>(); private

如果锁内有一个安全的对象引用,而锁外没有同步就开始使用它,那该怎么办?解除锁定后,其他线程无法使用此对象。是否可以保证处理器中没有过时或缓存的数据?在得到锁之前,我会看到其他线程所做的所有更改吗?一般来说,它是线程安全的吗

让我展示一些代码

private List<string> list1 = new List<string>();
private List<string> list2 = new List<string>();
private List<string> list;
private bool flagList;

private readonly object locker = new object();

// in constructor
list = list1;
flagList = true;

public void Write(string data)
{
    lock (locker)
    {
        list.Add(data);
    }
}

// guaranteed non-reentrant code
private void TimerElapsed(object obj)
{
    List<string> l;
    lock (locker)
    {
        l = list;
        list = (flagList) ? list2 : list1;
        flagList = !flagList;
    }
    // process l Count/items here, then clear
    ...
    l.Clear();
    // restart timer
}
private List list1=new List();
私有列表list2=新列表();
私人名单;
私人布尔旗舰名单;
私有只读对象锁定器=新对象();
//在构造函数中
列表=列表1;
flagList=true;
公共无效写入(字符串数据)
{
锁(储物柜)
{
列表。添加(数据);
}
}
//保证不可重入码
私有无效时间重复(对象obj)
{
清单l;
锁(储物柜)
{
l=列表;
列表=(flagList)?列表2:list1;
flagList=!flagList;
}
//在此处处理l计数/项目,然后清除
...
l、 清除();
//重新启动计时器
}

因此,
timerecursed
保证一次只运行一次,并且
Write
可以在任何时候被多个不同的线程调用。问题是:
l
是否总是看到自上一次
timerecursed
以来进行的所有添加?

因此,如果这是您的问题,
Write
timerecursed
中的lock语句块内时不会添加任何项。 不过,您可以通过只使用一个列表来简化代码

    private List<string> list = new List<string>();

    private readonly object locker = new object();

    public void Write(string data)
    {
        lock (locker)
        {
            list.Add(data);
        }
    }

    // guaranteed non-reentrant code
    private void TimerElapsed(object obj)
    {
        List<string> copy;
        lock (locker)
        {
            copy = list;
            list = new List<string>();
        }

        // process copy
    }
private List=new List();
私有只读对象锁定器=新对象();
公共无效写入(字符串数据)
{
锁(储物柜)
{
列表。添加(数据);
}
}
//保证不可重入码
私有无效时间重复(对象obj)
{
清单副本;
锁(储物柜)
{
副本=列表;
列表=新列表();
}
//过程副本
}

在您的示例中,
l.Clear()
和其他处理在您释放锁后进行。这意味着在执行
l.Clear()
时,另一个线程可能已经获得了锁并开始执行代码

因为您已经释放了锁,所以在处理另一个线程时,可以调用
Write
。计时器可能会再次消失,另一个线程可能会获取锁,获取对同一列表的引用,并开始处理它。这可能会发生多次。(您不想对计时器间隔的长度和处理时间做任何假设。)

与其访问
列表
线程安全,您真正需要的是集合本身是线程安全的。否则,正如您所说的,任何其他线程如果“安全地”获得引用,就可以在需要时对该对象执行任何操作

一种解决方案是使用
ConcurrentQueue
。这允许您在单独的线程上安全地插入和移除,并为您处理内部锁定

public class UsesConcurrentQueue
{
    private readonly ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();

    public void Write(string data)
    {
        _queue.Enqueue(data);
    }

    private void TimerElapsed(object obj)
    {
        string dataToProcess = null;
        while(_queue.TryDequeue(out dataToProcess))
        {
            // process each string;
        }
    }
}
公共类使用ConcurrentQueue
{
私有只读ConcurrentQueue _queue=新ConcurrentQueue();
公共无效写入(字符串数据)
{
_排队(数据);
}
私有无效时间重复(对象obj)
{
字符串dataToProcess=null;
while(_queue.TryDequeue(out dataToProcess))
{
//处理每个字符串;
}
}
}
现在,当您将一个项目添加到队列中时,您不必关心另一个线程是否正在从同一队列中读取

当计时器过期并且您开始处理时,您将继续处理,直到队列为空。您可以在处理过程中不断向队列中添加项目,这样就可以了

如果需要确保不在两个线程上处理队列(假设计时器在第一次完成处理之前再次过期),则可以在输入该方法时停止计时器,并在退出时重新启动

或者假设您有某种原因想要处理一个项目列表,而不是一次处理一个项目,就像批量处理一样。然后你可以这样做:

    private void TimerElapsed(object obj)
    {
        string dataToProcess = null;
        var list = new List<string>();
        while (_queue.TryDequeue(out dataToProcess))
        {
            list.Add(dataToProcess);
        }
        // Now you've got a list.
    }
private void timerecursed(对象obj)
{
字符串dataToProcess=null;
var list=新列表();
while(_queue.TryDequeue(out dataToProcess))
{
list.Add(dataToProcess);
}
//现在你有了一份清单。
}

现在您有了一个项目的
列表
,但它是一个局部变量,而不是类级别的字段。因此,您可以对它进行操作,甚至可以将它发送到另一个方法,而不必担心其他线程会修改它

当然,这是一个正确的观点。但我的要求不同。比如说,
copy.Count
总是反映实际的计数。或者它可以缓存在处理器的缓存中,并返回自上次调用以来的过时数据。
copy
在您将锁留在
timerecursed
后将不会获得新项目,因为
Write
将已将项目添加到新列表实例中。因此,它将返回创建副本时
列表中的项目数,这将是自上次
timerecursed
调用创建该实例以来调用
Write
的所有项目。感谢您的回复!我必须提到的是,在前一个计时器完成它的工作之前,它不能再次消失。这就是我启动和重新启动它的方式。我只声明作为注释-这是不可重入的代码。我想要实现的是最小化
写入
执行时间。所以我不希望使用并发查询,因为它会减慢
添加
。而完整的列表拷贝也会造成不必要的内存压力,因为列表可能会变大。因此,问题不在于如何做到与书面代码相同,而在于书面代码是否正确/线程安全。