-1

在使用名为GlobalID我用作计数器的全局整数时,以下代码可以正常工作:

MyFunction(myClass thisClass, Int ID)
{   
     ListOfMyClass.add(thisClass)

     If (thisClass.IsPropertyValueAboveZero)
     {
          ID = GlobalID;
     }

     GlobalID++;

     Foreach (class someClass in ListOfClasses)
     {
           MyClass newClass = new MyClass(GlobalID, ID)
           MyFunction(newClass, ID)  //Recursive Call
     }
 }

GlobalID每次调用该函数时,我基本上都将其用作递增计数器。计数器在第一次执行时被分配一个起始位置。我正在使用全局变量,因为我想确保每次传递的 ID 都准确增加,而不管执行进入或离开递归调用。从分配全局变量起始位置的 ForEach 循环调用此函数(第一次....)

我的目标是使用 Parallel.ForEach 进行初始调用,而不是常规的 For Each 循环。我的问题与柜台有关。我不知道如何在多个线程中管理该计数器。如果我将它作为变量传递给函数,我相信我会有一个不准确的较低/使用的数字离开递归循环。全局变量确保下一个数字高于前一个数字。这thisClass.IsPropertyValueAboveZero只是基于条件语句描述动作的任意方式。它对其余代码没有有意义的引用。

如果我有多个线程的计数器的起始位置不同,我该如何确保这个线程安全?我目前看到的唯一方法是手动编写同一函数和计数器的多个版本并使用TaskFactory

4

3 回答 3

4

对于线程安全计数,请使用 Interlocked 方法:System.Threading.Interlocked.Increment(ref globalID)

private static int globalID;

// Be very careful about using a property to expose the counter
// do not *use* this value directly, but can be useful for debugging
// instead use the return values from Increment, Decrement, etc.
public int UnsafeGlobalID { get { return globalID; } }

注意:您需要为ref. 可以通过属性公开字段,但在代码中可能会出现问题。最好使用互锁方法,例如围绕需要以同步方式获取值的逻辑的Interlocked.CompareExchange显式语句。,等lock的返回值通常应该在逻辑中使用。IncrementDecrement

我的问题与柜台有关。我不知道如何在多个线程中管理该计数器。如果我将它作为变量传递给函数,我相信我会有一个不准确的较低/使用的数字离开递归循环。全局变量确保下一个数字高于前一个数字。

如果我有多个线程的计数器的起始位置不同,我该如何确保这个线程安全?我目前看到的唯一方法是手动编写同一函数和计数器的多个版本并使用TaskFactory

递归调用MyFunction(newClass, ID)的值为ID,但我不确定If (thisClass.IsPropertyValueAboveZero)应该做什么。是为了确保您有一个非零起点吗?如果是这样,最好在此函数之外的初始调用之前确保它是非零的。

此外,foreach 循环中的逻辑对我来说也没有意义。在willMyClass newClass = new MyClass(GlobalID, ID)ID参数值,或者如果IsPropertyValueAboveZero为真,它将是 的当前值GlobalID。所以ID通常会小于GlobalID因为在循环GlobalID之前递增。foreach我认为您在GlobalID不需要时会通过。

// if IsPropertyValueAboveZero is intended to start above zero
// then you can just initialize the counter to 1
private static int globalID = 1;

public void MyFunction(myClass thisClass, int id)
{
    // ListOfMyClass and ListOfClasses are probably not thread-safe
    // and you may need to add locks around the Add and get a copy
    // of ListOfClasses before the foreach enumeration
    // You may want to look at https://stackoverflow.com/a/6601832/29762
    ListOfMyClass.Add(thisClass); 

    foreach (class someClass in ListOfClasses)
    {
        int newId = System.Threading.Interlocked.Increment(ref globalID);
        MyClass newClass = new MyClass(newId);
        MyFunction(newClass, newId);  //Recursive Call
    }
}
于 2017-09-15T13:52:34.733 回答
2

如果您将变量用作Interlocked.Increment作品的计数器标准用法(请参阅C# Thread safe fast(est) counter)。由于您的代码实际上想要使用计数器的值,因此您必须非常小心地获得正确的值,因为必须同时保护从该值读取的值。

因为您只需要在增加值时读取值就Interlocked.Increment足够了。确保永远不要检查或使用 value 或GlobalID直接,而多个线程可以修改它:

var ID = someValue; 
var newValue = Interlocked.Increment(ref GlobalID);
if (thisClass.IsPropertyValueAboveZero)
{
      // you can't use GlobalID directly here because it could be already incremented more
      ID = newValue - 1; // note -1 to match your original code.

}

否则代码会变得更加棘手 - 例如,如果您有时只想增加全局值,但每次调用都使用它(这可能不是您的情况,因为示例显示始终使用较少的增量)而不是您必须将当前值单独传递给您的函数。同样在这种情况下,传递给函数和实际计数器的值将彼此无关。如果用例不是“仅在增加时使用价值”,您可能应该查看您想要实现的目标 - 很可能您需要一些其他数据结构。请注意,lock仅用于保护对计数器的读取和写入不会解决线程安全问题,因为您无法预测您将要读取的值(可能会被其他线程多次递增)。

注意:帖子中的示例代码显示了一些旧 ID 值和最新计数器值的一些不清楚的用法。如果代码实际上反映了您想要做的事情,将“计数调用次数”与“对象 ID”分开会很有用。

于 2017-09-15T14:27:35.210 回答
-1

对于单个计数器的线程安全整数递增,请考虑使用Interlocked.Increment.

将变量存储在静态中:

public static int Bob;

然后在静态函数中增加它:

public static int IncrementBob()
{
    return Interlocked.Increment(ref Bob);
}

任何时候你想增加,打电话IncrementBob

于 2017-09-15T13:52:38.400 回答