Я должен синхронизировать доступ потока к интервалу?

Примечания: в случае, если кто-то отказался проголосовать за мой ответ, у меня есть кое-что объяснить здесь.

  1. Всем нравятся короткие и простые ответы. Однако иногда реальность не так проста.
  2. Вернуться к моему ответу. Я знаю, что shutil.rmtree() можно использовать для удаления дерева каталогов. Я использовал это много раз в моих собственных проектах. Но вы должны понимать, что сам каталог также будет удален с помощью shutil.rmtree() . Хотя это может быть приемлемо для некоторых, это неправильный ответ для удаления содержимого папки (без побочных эффектов) .
  3. Я покажу вам пример побочных эффектов. Предположим, что у вас есть каталог с настроенными владельцем и битами режима, где много содержимого. Затем вы удаляете его с помощью shutil.rmtree() и восстанавливаете его с помощью os.mkdir(). И вместо этого вы получите пустой каталог с по умолчанию (унаследованным) владельцем и битами режима. Несмотря на то, что у вас может быть право удалять содержимое и даже каталог, вы не сможете установить первоначальные биты владельца и режима в каталоге (например, вы не являетесь суперпользователем).
  4. Наконец, наберитесь терпения и прочитайте код . Это долго и некрасиво (видно), но доказано, что оно надежно и эффективно (используется).

Вот длинное и безобразное, но надежное и эффективное решение.

Это решает несколько проблем, которые не были рассмотрены другими ответчиками:

  • Он правильно обрабатывает символические ссылки, в том числе не вызывает shutil.rmtree() для символической ссылки (которая пройдет os.path.isdir() проверить, ссылается ли он на каталог; даже результат из os.walk() также содержит символические связанные каталоги).
  • Хорошо обрабатывает файлы только для чтения.

Вот код (единственная полезная функция - clear_dir()):

import os
import stat
import shutil


# http://stackoverflow.com/questions/1889597/deleting-directory-in-python
def _remove_readonly(fn, path_, excinfo):
    # Handle read-only files and directories
    if fn is os.rmdir:
        os.chmod(path_, stat.S_IWRITE)
        os.rmdir(path_)
    elif fn is os.remove:
        os.lchmod(path_, stat.S_IWRITE)
        os.remove(path_)


def force_remove_file_or_symlink(path_):
    try:
        os.remove(path_)
    except OSError:
        os.lchmod(path_, stat.S_IWRITE)
        os.remove(path_)


# Code from shutil.rmtree()
def is_regular_dir(path_):
    try:
        mode = os.lstat(path_).st_mode
    except os.error:
        mode = 0
    return stat.S_ISDIR(mode)


def clear_dir(path_):
    if is_regular_dir(path_):
        # Given path is a directory, clear its content
        for name in os.listdir(path_):
            fullpath = os.path.join(path_, name)
            if is_regular_dir(fullpath):
                shutil.rmtree(fullpath, onerror=_remove_readonly)
            else:
                force_remove_file_or_symlink(fullpath)
    else:
        # Given path is a file or a symlink.
        # Raise an exception here to avoid accidentally clearing the content
        # of a symbolic linked directory.
        raise OSError("Cannot call clear_dir() on a symbolic link")
20
задан Michael Petrotta 21 June 2012 в 16:32
поделиться

8 ответов

A ++ operator is not atomic in C# (and I doubt it is guaranteed to be atomic in C++) so yes, your counting is subject to race conditions.

Use Interlocked.Increment and .Decrement

System.Threading.Interlocked.Increment(ref _reportsRunning);
try 
{
  ...
}
finally
{
   System.Threading.Interlocked.Decrement(ref _reportsRunning);
}
27
ответ дан 29 November 2019 в 23:00
поделиться

No, you need to synchronize access. On Windows you can do this easily with InterlockedIncrement() and InterlockedDecrement(). I'm sure there are equivalents for other platforms.

EDIT: Just noticed the C# tag. Do what the other guy said. See also: I've heard i++ isn't thread safe, is ++i thread-safe?

2
ответ дан 29 November 2019 в 23:00
поделиться

Итак, к вопросу: когда я был изучая многопоточность на C ++, я был учил, что тебе не нужно синхронизировать вызовы для увеличения и операции декремента, потому что они были всегда одна инструкция по сборке и поэтому это было невозможно для поток, который должен быть отключен во время разговора. Правильно ли меня учили, и если да, то как come that doesn't hold true for C#?

This is incredibly wrong.

On some architectures, like x86, there are single increment and decrement instructions. Many architectures do not have them and need to do separate loads and stores. Even on x86, there is no guarantee the compiler will generate the memory version of these instructions - it'll likely load into a register first, especially if it needs to do several operations with the result.

Even if the compiler could be guaranteed to always generate the memory version of increment and decrement on x86, that still does not guarantee atomicity - two CPU's could modify the variable simultaneously and get inconsistent results. The instruction would need the lock prefix to force it to be an atomic operation - compilers never emit the lock variant by default since it is less performant since it guarantees the action is atomic.

Consider the following x86 assembly instruction:

inc [i]

If I is initially 0 and the code is run on two threads on two cores, the value after both threads finish could legally be either 1 or 2, since there is no guarantee that one thread will complete its read before the other thread finishes its write, or that one thread's write will even be visible before the other threads read.

Changing this to:

lock inc [i]

Will result in getting a final value of 2.

Win32's InterlockedIncrement and InterlockedDecrement and .NET's Interlocked.Increment and Interlocked.Decrement result in doing the equivalent (possibly the exact same machine code) of lock inc.

20
ответ дан 29 November 2019 в 23:00
поделиться

Увеличение int - это одна инструкция, но как насчет загрузки значения в регистр?

То, что фактически делает i ++ :

  1. загружает i в регистр
    • увеличить регистр
    • выгрузить регистр в i

Как вы можете видеть, есть 3 (на других платформах это может быть иначе) инструкции, которые на любом этапе ЦП может переключить контекст в другой поток оставляя вашу переменную в неизвестном состоянии.

Для решения этой проблемы следует использовать Interlocked.Increment и Interlocked.Decrement .

3
ответ дан 29 November 2019 в 23:00
поделиться

Вас неправильно учили.

Существует оборудование с атомарным целочисленным приращением, поэтому вполне возможно, что то, чему вас учили, подходило для оборудования и компилятора, которые вы использовали в то время. Но в целом в C ++ вы даже не можете гарантировать, что при увеличении энергонезависимой переменной память будет записываться последовательно с чтением, не говоря уже о атомарном процессе чтения.

4
ответ дан 29 November 2019 в 23:00
поделиться

x ++, вероятно, не является атомарным, но ++ x может быть (не уверен, что сразу, но если учесть разницу между пост- и предварительным инкрементом, должно быть ясно, почему pre- больше поддаются атомарности).

Еще важнее то, что если каждый из этих запусков занимает секунду, то время, добавленное блокировкой, будет шумом по сравнению с временем выполнения самого метода. В этом случае, вероятно, не стоит обезумевать, пытаясь снять блокировку - у вас есть правильное решение с блокировкой, которое, вероятно, не будет иметь видимой разницы в производительности по сравнению с неблокирующим решением.

0
ответ дан 29 November 2019 в 23:00
поделиться

Любой вид операции увеличения / уменьшения на языке более высокого уровня (и да, даже C является более высоким уровнем по сравнению с машинными командами) не является атомарным по своей природе. Однако каждая процессорная платформа обычно имеет примитивы, которые поддерживают различные атомарные операции .

Если ваш лектор имел в виду машинные инструкции, операции увеличения и уменьшения, вероятно, будут атомарными. Тем не менее, это не всегда верно на постоянно растущих многоядерных платформах сегодня, если только они не гарантируют когерентности .

Языки более высокого уровня обычно реализуют поддержку атомарных транзакций ] с использованием низкоуровневых команд атомарной машины. Это обеспечивается как механизм блокировки API более высокого уровня.

1
ответ дан 29 November 2019 в 23:00
поделиться

На однопроцессорной машине , если виртуальная память не используется, x ++ (rvalue игнорируется), скорее всего, преобразуется в одну атомарную инструкцию INC на архитектурах x86 (если x длинный, операция атомарна только при использовании 32-битного компилятора). Кроме того, movsb / movsw / movsl являются атомарными способами перемещения байта / слова / длинного слова; компилятор не склонен использовать их как обычный способ присвоения переменных, но можно иметь служебную функцию атомарного перемещения. Диспетчер виртуальной памяти можно было бы написать таким образом, чтобы эти инструкции вели себя атомарно, если во время записи происходит сбой страницы, но я не думаю, что это обычно гарантируется.

На многопроцессорной машине все ставки отключены, если не используются явные взаимосвязанные инструкции (вызываемые через специальные вызовы библиотеки). Самая универсальная из доступных инструкций - это CompareExchange. Эта инструкция изменит ячейку памяти, только если она содержит ожидаемое значение; он вернет значение, которое имел, когда решал, изменять его или нет. Если кто-то желает «xor» переменной с 1, можно сделать что-то вроде (в vb.net)

  Dim OldValue as Integer
  Do
    OldValue = Variable
  While Threading.Interlocked.CompareExchange(Variable, OldValue Xor 1, OldValue)  OldValue

. Этот подход позволяет выполнить любой вид атомарного обновления переменной, новое значение которой должно зависеть от старого значения. Для некоторых общих операций, таких как увеличение и уменьшение, есть более быстрые альтернативы, но CompareExchange позволяет реализовать и другие полезные шаблоны.

Важные предостережения: (1) Делайте петлю как можно короче; чем длиннее цикл, тем больше вероятность того, что другая задача затронет переменную во время цикла, и тем больше времени будет потрачено впустую каждый раз, когда это произойдет; (2) определенное количество обновлений, произвольно разделенных между потоками, всегда будет завершено, поскольку единственный способ принудительного повторного выполнения цикла потоком - это если какой-либо другой поток добился полезного прогресса; Однако если некоторые потоки могут выполнять обновления, не продвигаясь вперед к завершению, код может быть заблокирован в реальном времени.

0
ответ дан 29 November 2019 в 23:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: