Предположим, что существует структура данных, такая как std::vector и глобальная переменная int syncToken, инициализированная нулем. Также дано, ровно два потока как читатель/писатель, почему следующий (псевдо) код (в) действителен?
void reader_thread(){
while(1){
if(syncToken!=0){
while(the_vector.length()>0){
// ... process the std::vector
}
syncToken = 0; // let the writer do it work
}
sleep(1);
}
}
void writer_thread(){
while(1){
std::string data = waitAndReadDataFromSomeResource(the_resource);
if(syncToken==0){
the_vector.push(data);
syncToken = 1; // would syncToken++; be a difference here?
}
// drop data in case we couldn't write to the vector
}
}
Хотя этот код не эффективен (по времени), насколько я могу видеть, что код действителен, потому что два потока синхронизируются только по значению глобальной переменной таким образом, что поведение undefined не может привести. Единственная проблема может возникнуть при одновременном использовании вектора, но это не должно происходить из-за того, что только переключение между нулем и одним значением синхронизации, правильно?
UPDATE Поскольку я допустил ошибку, задав только вопрос "да/нет", я обновил свой вопрос, почему в надежде получить конкретный случай в качестве ответа. Также кажется, что сам вопрос рисует неправильную картину, основанную на ответах, поэтому я подробно расскажу о том, что моя проблема/вопрос с указанным выше кодом.
Заранее, я хочу указать, что я прошу указать конкретный пример использования/пример/доказательство/подробное объяснение, которое демонстрирует именно то, что не синхронизируется. Даже пример кода C, который позволяет примерному счетчику вести себя не монотонно, будет просто отвечать на вопрос "да/нет", но не почему! Меня интересует, почему. Итак, если вы представите пример, демонстрирующий, что у него есть проблема, мне интересно, почему.
В силу (my) определение выше кода должно быть названо синхронизированным тогда и только тогда, когда код внутри оператора if, исключая назначение syncToken в нижней части блока if, может выполняться только одним из этих двух заданных потоков в заданное время.
Основываясь на этой мысли, я ищу пример, возможно, на основе ассемблера, где оба потока выполняют блок if в то же время, то есть они не синхронизированы или не синхронизированы.
В качестве справки рассмотрим соответствующую часть кода ассемблера, созданного gcc:
; just the declaration of an integer global variable on a 64bit cpu initialized to zero
syncToken:
.zero 4
.text
.globl main
.type main, @function
; writer (Cpu/Thread B): if syncToken == 0, jump not equal to label .L1
movl syncToken(%rip), %eax
testl %eax, %eax
jne .L1
; reader (Cpu/Thread A): if syncToken != 0, jump to Label L2
movl syncToken(%rip), %eax
testl %eax, %eax
je .L2
; set syncToken to be zero
movl $0, syncToken(%rip)
Теперь моя проблема в том, что я не вижу способа, почему эти инструкции могут выйти из синхронизации.
Предположим, что оба потока выполняются на своем собственном ядре ЦП, так как Thread A работает на ядре A, Thread B работает на ядре B. Инициализация является глобальной и выполняется до начала обоих потоков, поэтому мы можем игнорировать инициализацию и предполагать начало обоих потоков с syncToken = 0;
Пример:
- Cpu A: movl syncToken (% rip),% eax
- Cpu A: контекстный переключатель (сохранение всех регистров)
- Cpu B: movl syncToken (% rip),% eax
- Cpu B: testl% eax,% eax
- Cpu B: jne.L1; это false = > execute writer, если block
- Cpu B: контекстный переключатель
- Cpu A: контекстный переключатель в поток (восстановление всех регистров)
- Cpu A: testl% eax,% eax
- Cpu A: je.L2; это false = > не выполняется, если блок
Честно говоря, я построил пример, который хорошо работает, но он демонстрирует, что я не вижу способа, почему переменная должна выйти из синхронизации, так что оба потока выполняют блок if одновременно. Моя точка зрения: хотя контекстный переключатель приведет к несогласованности между% eax и фактическим значением syncToken в ОЗУ, код должен делать правильную вещь и просто не выполнять блок if, если это не единственный поток, разрешенный для запуска он.
ОБНОВЛЕНИЕ 2 Можно предположить, что syncToken будет использоваться только как в коде, как показано. Никакая другая функция (например, waitAndReadDataFromSomeResource) не может использовать ее каким-либо образом.
ОБНОВЛЕНИЕ 3 Отпустите еще один шаг, задав несколько разных вопросов: возможно ли синхронизировать два потока, один читатель, один сценарий с использованием int syncToken, чтобы потоки не выходили из синхронизации все время, одновременно выполняя блок if? Если да - это очень интересно ^^ Если нет - почему?