Вы не можете реально проверить безопасность потоков. Все, что вы можете сделать, это показать, что ваш код не является поточно-ориентированным, но если вы знаете, как это сделать, вы уже знаете, что делать в своей программе, чтобы исправить эту конкретную ошибку. Проблема заключается в том, что ошибки, о которых вы не знаете, и как бы вы написали тесты для них? Кроме того, проблемы с многопоточностью найти гораздо сложнее, чем другие проблемы, так как отладка уже может изменить поведение программы. Вещи будут отличаться от одной программы к другой, от одной машины к другой. Количество процессоров и процессорных ядер, количество и тип программ, работающих параллельно, точный порядок и время выполнения событий в программе - все это и многое другое будет влиять на поведение программы. [Я действительно хотел добавить фазу луны и тому подобное в этот список, но вы меня поняли.]
Мой совет - перестать воспринимать это как проблему реализации и начать рассматривать ее как проблему разработки программы. Вам необходимо изучить и прочитать все, что вы можете найти о многопоточности, независимо от того, написано ли это для Delphi или нет. В конце концов, вам нужно понять основополагающие принципы и правильно применять их в своем программировании. Примитивы, такие как критические секции, мьютексы, условия и потоки, - это то, что предоставляет ОС, и большинство языков заключают их в свои библиотеки (это игнорирует такие вещи, как зеленые потоки, как, например, в Erlang), но это хорошая точка зрения, чтобы начать с ).
Я бы сказал, начните со статьи Википедии о потоках и пролистайте связанные статьи. Я начал с книги "Многопоточное программирование Win32" Аарона Коэна и Майка Вудринга - она вышла из печати, но, возможно, вы найдете что-то подобное.
Редактировать: Позвольте мне вкратце ответить на ваш отредактированный вопрос. Весь доступ к данным, которые не только для чтения, должны быть должным образом синхронизированы, чтобы быть потокобезопасным, и сортировка списка не является операцией только для чтения. Очевидно, что необходимо добавить синхронизацию для всех обращений к списку.
Но с увеличением количества ядер в системе постоянная блокировка будет ограничивать объем работы, которую можно выполнить, поэтому неплохо было бы искать другой способ разработки вашей программы. Одна из идей состоит в том, чтобы ввести в вашу программу как можно больше данных только для чтения - блокировка больше не требуется, поскольку весь доступ только для чтения.
Я обнаружил, что интерфейсы очень полезны при разработке многопоточных программ. Интерфейсы могут быть реализованы так, чтобы иметь только методы доступа только для чтения к внутренним данным, и если вы будете придерживаться их, вы можете быть совершенно уверены, что многие потенциальные ошибки программирования не возникнут. Вы можете свободно делиться ими между потоками, а поточно-ориентированный подсчет ссылок обеспечит правильное освобождение реализующих объектов, когда последняя ссылка на них выйдет из области видимости или ей будет присвоено другое значение.
Что вы делаете, это создаете объекты, которые происходят от TInterfacedObject. Они реализуют один или несколько интерфейсов, которые обеспечивают только доступ только для чтения к внутренним объектам объекта, но они также могут предоставлять открытые методы, которые изменяют состояние объекта. При создании объекта вы сохраняете переменную типа объекта и переменную указателя интерфейса. Таким образом, управление временем жизни легко, поскольку объект будет автоматически удален при возникновении исключения. Переменная, указывающая на объект, используется для вызова всех методов, необходимых для правильной настройки объекта. Это изменяет внутреннее состояние, но так как это происходит только в активном потоке, нет никакой возможности для конфликта. Как только объект правильно настроен, вы возвращаете указатель интерфейса на вызывающий код, и поскольку впоследствии нет доступа к объекту, кроме как через указатель на интерфейс, вы можете быть уверены, что может быть выполнен только доступ только для чтения. Используя эту технику, вы можете полностью снять блокировку внутри объекта.
Что если вам нужно изменить состояние объекта? Вы не делаете, вы создаете новый, копируя данные из интерфейса, и затем изменяете внутреннее состояние новых объектов. Наконец вы возвращаете ссылочный указатель на новый объект.
При использовании этого вам потребуется блокировка только там, где вы получаете или устанавливаете такие интерфейсы. Это можно сделать даже без блокировки, используя функции атомного обмена. См. это сообщение в блоге от Primoz Gabrijelcic для аналогичного случая использования, когда установлен указатель интерфейса.