Немного туп(л,)
84 subscribers
179 photos
26 videos
40 links
Маленький бложег С++ программиста, увлекающегося Rust'ом. Не столько про пргрмрвне, а вообще.
Download Telegram
Channel photo updated
Ну привет. Меня не зовут, я сам прихожу Пашечка.
Мне, во-первых, надоело в несколько чатов копировать свои истории, а во-вторых, я подумал, что они не то чтобы прям всем интересны 🌚
Поэтому мой шитпостинг будет теперь здесь.

В названии, картинке и описании канала небольшая игра слов: "let L tuple" можно прочитать почти как "little tuple" 🌚 И это немного тупо, да х)
Для тех, кто не знает ни меня, ни про меня:
Я - С++ программист, субъективно middle уровня, а объективно, надо выгнать меня мести улицы. В свободное время увлекаюсь попытками написать что-то на Rust'е. Что обидно, интересуюсь я Rust'ом примерно с 2014 года, но что-то более менее рабочее могу написать с 2019 только 🌚 Лоботряс и лентяй ибо.

На данный момент я работаю на предприятии ВПК РФ, всякие тренажёры разрабатываем, системы трёхмерной визуализации, симуляции, и прочее. По идее, деятельность моего отдела крайне близка к геймдеву, но лично я больше занимаюсь околосистемными вещами, многопоточностью, системой сборки, прикручиванием сторонних библиотек и их отладкой. Ну и у нас в отделе я проповедник плюсового стандарта, хотя и далеко не спец в его толковании)

Хочу свалить куда-то, где основным языком будет Rust, но пока не получается.
👍1
#truestory #cpp #jobbing

Ну а теперь история, ради которой этот канал был создан, и она про отладку сторонних библиотек.

Мы используем boost. Но мало того, что мы используем boost, мы используем старый boost, а именно версии 1.62, потому что он поставляется с Astra Linux 1.6. А Астра наша приоритетная целевая ОСь. (На самом деле под виндой у нас boost v1.64, но сути это не меняет)

И вот затащили мы некоторое время назад open-license-manager, в составе которого библиотека licensecc и генератор лицензий lccgen. И вот последний тоже зависит от boost'а. Ограничений на версию в CMakeLists.txt не установлено, а в документации проекта сказано использовать >= 1.57, и с нашей старой версией (напомню, что это 1.62) оно в принципе собирается, но только если отключить сборку тестов, но кому эти тесты нужны, правда?
Однако, при запуске, падает во время разбора аргументов командной строки (используется boost::program_options). Опытным путём выяснили, что если подкинуть версию boost'а 1.71, то падения пропадают. Кажется, даже тесты собираются, но это не точно, они же нинужны, поэтому я не перепроверял.

В принципе, мы генератор лицензий заказчикам не отдаём, поэтому собрали это дело только под окошками с новым boost'ом, забив на сертификацию, и всё бы ничего, но вот теперь потребовалось заиметь генератор и под Астру...
#jobbing

Маленькое отступление: почему Астра, это проблема при выборе зависимостей.

Astra Linux Special Edition, это операционная система для использования в критических инфраструктурах с повышенными требованиями к безопасности и защите данных. Она сертифицирована в ФСБ, ФСТЭК, и где-то там ещё, короче имеет все возможные отечественные сертификаты, что позволяет использовать её для работы с документами под грифом вплоть до "особой важности". Наше ПО тоже проходит такую же сертификацию (хотя скорее всего "облегчённую", не до конца уверен). А для того, чтобы получить сертифицированный продукт, необходимо использовать только сертифицированные инструменты и сторонние библиотеки. Теоретически можно самостоятельно собрать и новый boost, и новый cmake, и новое что угодно, но это тоже придётся сертифицировать. А сертификация стоит денег, и количество этих денег зависит от количества сертифицируемого кода. То есть, хочешь собирать больше зависимостей - плати больше денег. Поэтому мы сильно ограничены в инструментах.

Как-то я прикидывал, что нам будет стоить собрать по нормальному, по правильному, новые драйвера для AMD GPU, и вышло всё очень объёмно с тонной ненужных в рантайме зависимостей. Пришлось собирать кое-как, и при установке наших драйверов, по факту, портится система.
Немного туп(л,)
#truestory #cpp #jobbing Ну а теперь история, ради которой этот канал был создан, и она про отладку сторонних библиотек. Мы используем boost. Но мало того, что мы используем boost, мы используем старый boost, а именно версии 1.62, потому что он поставляется…
Возвращаемся к lccgen и boost.

Утилита lccgen среди аргументов принимает список фич (features), которые добавляются в лицензию. То есть вы можете выдать лицензию только на ваш продукт, а можете разрабатывать дополнительные платные плагины, и формировать лицензию для некоторого набора этих плагинов. Эти фичи задаются через coma-separated list: -f Feat1,Feat2,Feat3. И исключение выбрасывается здесь:
po::store(
po::command_line_parser(opts) // Создаём парсер. opts - это вектор строк, куда собраны все ещё не обработанные аргументы
.options(project_desc) // Добавляем описание (для вывода хэлпа и привязки переданных аргументов к их описанию)
.run(), // Запускаем парсер
vm); // Перекладываем данные в variables_map

Падение происходит только если пользователь указал список фич, все остальные аргументы обрабатываются нормально. Поэтому, чтобы осознать, что не так, надо найти описание для списка фич:
// В каком-то хэдере
#define PARAM_FEATURE_NAMES "feature-names"

// Конкретно описание опций
po::options_description license_desc("license issue options");
// [...]
license_desc.add_options()
// [...]
(PARAM_FEATURE_NAMES ",f", po::value<boost::optional<std::string>>(),
"Feature names: comma separate list of project features to enable. if not specified will be taken as project "
"name.")
// [...]
;

Господи, как же это сделано неудобно! Функтор на функторе, блджад! Какой же clap офигенный на этом фоне!
Немного поменяю форматирование и уберу лишнее, оставив только суть:
license_desc.add_options()(
"feature-names,f" // Имя ключа
po::value<boost::optional<std::string>>(), // Тип данных аргумента
"<option-description>" // Описание
);

К слову, это единственный параметр, который принимает значение optional<string>, в остальных обычный string. Почему авторы так сделали? Не знаю. Но лучше бы они так не делали.
👍1
Немного туп(л,)
Возвращаемся к lccgen и boost. Утилита lccgen среди аргументов принимает список фич (features), которые добавляются в лицензию. То есть вы можете выдать лицензию только на ваш продукт, а можете разрабатывать дополнительные платные плагины, и формировать лицензию…
Дальше, чтобы разобраться основательнее, в чём же разница, я собрал lccgen с обеими версиями boost'а, и начал в отладчике ходить по ним параллельно.
Если кратко, внутри проблемного вызова ps::store() происходит следующее:
1. Перебираем все даденные опции
2. Создаём в vm пустой элемент (по факту boost::any с NULL значением)
3. Парсим в соответствии с семантикой, записанной в описании опции и перекладываем в созданный в vm элемент
И вот на последнем пункте появляются расхождения.
В boost'е заготовлено несколько шаблонных валидаторов. И вот старая версия в ходе работы попадает в
template<class T, class charT>
void validate(boost::any& v,
const std::vector< std::basic_string<charT> >& xs,
T*, long)
А новая в:
template<class T, class charT>
void validate(boost::any& v,
const std::vector<std::basic_string<charT> >& s,
boost::optional<T>*,
int)
В описании к первой указано, что это "специальная функция для компиляторов, которые не поддерживают partial template ordering". Нюанс в том, что компилятор используется один и тот же 🌚
Конкретная специализация шаблона для старого boost'а: validate<boost::optional<std::string>, char>, а для нового: validate<std::string, char>.
В новой версии выполнение уходит на второй круг валидации (в более "тонкую" и конкретную реализацию), где происходит финальная корректная запись.
А в старой версии используется lexical_cast, и вот как оно выглядит в конкретной специализированной версии:
boost::optional<std::string> lexical_cast(const std::string& arg)
{
auto result = boost::optional<std::string>();
if (!boost::conversion::detail::try_lexical_convert(arg, result)) {
boost::conversion::detail::throw_bad_cast<std::string, boost::optional<std::string>>();
}
return result;
}
Функция try_lexical_convert это сложная шаблонная магия, где вызывается другая сложная шаблонная магия, где всё сводится к:
i_interpreter_type i_interpreter;

// Disabling ADL, by directly specifying operators.
if (!(i_interpreter.operator <<(arg)))
return false;

o_interpreter_type out(i_interpreter.cbegin(), i_interpreter.cend());

// Disabling ADL, by directly specifying operators.
if(!(out.operator >>(result)))
return false;

return true;
i_interpreter_type и o_interpreter_type это урезанные istream и ostream. По факту здесь:
1. Создаётся basic_streambuf, который инициализируется нашей исходной строкой
2. Создаётся std::basic_istream, инициализированный буфером, созданным на 1 шаге
3. Вызывается operator>>(std::basic_istream&, boost::optional<T>&), где T - std::string. Не знаю, важно или нет, но для std::string нет явной специализации.

В этом операторе происходит следующее:
1. Если первый символ в потоке пробел, то записываем значение.
2. Если первые два символа в потоке "--", то присваиваем None.
3. В противном случае - устанавливаем failbit.
А failbit, это значит, что запись не удалась. То есть lexical_cast выполнился с ошибкой, что приводит к throw bad_lexical_cast, которое перехватывается и выбрасывается error_with_option_name, который опять перехватывается, к нему добавляется контекстная информация, и происходит rethrow. По идее оно бы должно тоже где-то перехватиться, но по факту у меня этого почему-то не происходит. Да и не важно, на самом деле, почему.
Немного туп(л,)
Дальше, чтобы разобраться основательнее, в чём же разница, я собрал lccgen с обеими версиями boost'а, и начал в отладчике ходить по ним параллельно. Если кратко, внутри проблемного вызова ps::store() происходит следующее: 1. Перебираем все даденные опции 2.…
Итак. Что делать?
На вскидку вариантов два:
1. Сделать так, чтобы строка с фичами всегда начиналась с пробела. Я попробовал указывать -f " Feat1,Feat2" (обратите внимание на пробел в кавычках) и это реально работает. Программа работает как ожидается.
2. Перелопатить код lccgen, чтобы поменять optional<string> на string с пустым значением по умолчанию. Теоретически должно быть не сложно.
3. Пропатчить boost, убрав эту проверку
4. Обновить boost

Наблюдательный читатель обратит внимание, что пункты 3 и 4 out-of-range, поэтому их отметаем. Тем более, что 4 не реализуем по причине, которую я расписывал ранее, а 3 делать не стоит, ибо велик риск что-то сломать.
Править код lccgen?.. Нуууууу, мооожно конечно, но мне лень. Остаётся первое, сделать так, чтобы пробел был всегда.
И сделать это очень просто, благо что для запуска генератора используется sh/bat-скрипт, потому что аргументов ну очень много писать вручную (фич около 20 штук), плюс те, кто будет этим пользоваться, не очень-то умеют в консоль.

За сим история завершена. Два дня отладки внутри boost'а, тонна текста на этом канале (да и сам канал), и всего один дополнительный пробел в скрипте запуска lccgen.
#irl

Вот сидишь ты такой вне дома. Дома окажешься хз когда. На телефоне 8% зарядки осталось. Но у тебя же есть powerbank! Вот только кабель зарядный ты дома забыл 🥲
#haha #rust

Я тут между делом пилю свою плагиновую систему на расте. Главная её идея - safety. То есть, тот же (самый популярный) libloading - полностью unsafe, и от этого я хочу уйти. Если вкратце, уход от unsafe достигается за счёт добавления в плагин дополнительных метаданных и их проверке при загрузке. Ну как "достигается". Будет "достигаться", если я это дело допилю, потому что оно пока очень unsound :) Ломаю safe гарантию полностью!

Но суть вот в чём. Изначально я дал этому проекту название "plunger", потому что созвучно с "plugin", а я люблю созвучия и игру слов. Глянул в переводчик, он даёт перевод "поршень", меня устроило. При этом внутри я использовал всё тот же libloading, ибо зачем выдумывать лишнее, если база уже есть? А поддержку MacOS можно было бы и позже законтрибьютить. Но тут я столкнулся с нюансом, который мне не нравится совсем. И было принято решение запилить свою реализацию, благо это не rocket science. А для реализации надо придумать название. И пошёл я гуглить, что бы такое похожее на "поршень" придумать. И выяснил, что "plunger", с точки зрения англо-говорящего человека, это всё-таки не столько "поршень", сколько "вантуз". Не очень удачное имечко, для библиотеки 🌚

Штош, пойду искать что-то другое. Если кто предложит варианты, я только за 😊

P.S. К слову, может я ещё себя пересилю и не буду делать велосипед, а всё-таки обойдусь с помощью libloading, но имя проекту менять точно надо 😅
#irl

Я прокрастинатор. Одним из симптомов является огромный список для чтения. Например, на Хабре у меня почти полторы тысячи статей в закладках.
Кроме того я часто чем-то "загораюсь" и по-быстренькому вкатываюсь в тему, после чего, оценив объёмы материала, оставляю его "на потом".
Ещё мне часто приходится (по работе) разбираться с одной задачей, потом резко переключиться на другую, потом на третью. Не всегда это прям по рабочим вопросам, иногда просто интерес к чему-то возникает. И из-за этих переключений я не изучаю тему до конца, и оставляю материал "на потом".

Это я всё к чему. У меня в браузере телефона было 380 открытых вкладок. Триста восемьдесят, Карл! На телефоне! Года три, как я их "копил". В прошлом году, если не путаю, их стало 200 и я почистил, прочитав мелкое и удалив лишнее. А сейчас 380... И я себя пересилил. И удалил всё. А то браузер уж совсем начал лагать 🌚
#jobbing #truestory

Устоявшиеся правила надо соблюдать. Даже если они вам не нравятся. Их. Надо. Соблюдать. Так, о чём это я?

Сейчас мне приходится заниматься документацией. Ну, вот так получилось, оставим в стороне вопрос, почему это делаю я, а не специально обученные люди из методического отдела. Делается это всё в Word'е. Хорошее ПО, одно из лучших в своём классе, тут спорить глупо. Но не без проблем.
Вот одна из проблем сейчас и вылезла. Он завис. Ну, завис и завис, бывает, где-то ресурсы не почистили за собой, где-то out of bound допустили, всё понимаю, бывает. Да и разве ж это проблема? Инстинкт автосохранения у меня есть, Ctrl+S каждые пару минут на автомате, даже не задумываясь. Поэтому можно прибить процесс и запустить заново, потеряв всего пару минут работы, ведь так?

Нет. Не так. Начиная с Word 2013, выпущенной в 2012 году, для сохранения файла надо использовать сочетание Shift+F12, а не классическое Ctrl+S. И плевать на долгие годы создания пользовательского опыта, плевать на мнемонику "Save" у буквы 'S', которая раньше даже сбоку клавиши писалось (на старых клавиатурах). К чёрту всё!

Что, простите? Автосохранение? Так оно включено. Но в этот раз почему-то не сработало. Может быть потому что у меня был открыт параллельно файл с таким же именем, а может ещё что-то, не знаю. Но факт остаётся один: из-за "гениальности" какого-то UX-специалиста я потерял полтора часа работы. Желание что-либо делать пропало совсем.

Да, эти сочетания можно отредактировать. Но Word не мой основной инструмент, поэтому мне в его настройки лезть и не приходилось никогда.

Поэтому, пожалуйства, соблюдайте устоявшиеся правила. В мусульманских странах одевайтесь подобающе, в православный храм не ходите в шортах, а при разработке ПО используйте устоявшиеся сочетания клавиш для совершения классических действий.
#haha #irl #truestory

Зарплата инженера-программиста, пишущего на языке С++, middle уровня 🌚
Ну ладно, это аванс за половину июня 🥲
#repost

Это очень интересно.
☘️Британские учёные доказали, что люди славные котики
Что я наисследовала, пока изучала общение людей с голосовыми помощниками

Если вдруг вы помните, я планировала написать диплом об особенностях общения живых мясных людей с голосовыми помощниками. И даже собирала для этого дела респондентов — людей, которые общаются со своими Алисами, Марусями и Сири — или наоборот, из принципа не общаются. Щас расскажу, что я узнала.

☘️
Изначально у меня была идея выявить, что же такого в действиях Алисы заставляет пользователей воспринимать её как человека и так по-человечески на неё реагировать: например, защищать от обидчиков, благодарить или спрашивать, как у нее дела. Но с этой затеей меня остановила ноучница, потому что а) с Алисой всё понятно — она скушала много человеческих текстов, и на них хорошо научилась имитировать произвольный диалог [Вот тут можно об этом у Яндекса почитать]. Это и заставляет. б) интереснее будет проверить, что именно внутри людей сподвигает их очеловечивать железяку — какие их личностные особенности. в) на психфаке в дипломе нада исследовать людей, не-людей — ненада:)

☘️
Как говорится, у нас было 40 респондентов, 3 методики, анкета на 130+ вопросов и несколько гипотез. Не то, чтобы этого было достаточно для нормального исследования, но если решил защищать диплом, иди в своем решении до конца.

Короче, я придумала 15 утверждений, которые с разных сторон показывают особенности общения с голосовым помощником: нравится ли людям с ним болтать, испытывают ли они вину за грубость, важно ли для них, какое «мнение» сложится о них у голосового помощника и пр. — и попросила респондентов оценить, насколько они согласны или нет с этими утверждениям. А потом я замерила их уровень эмоционального интеллекта, сопереживания и социального самоконтроля по готовым методикам. И посмотрела, насколько эти штуки взаимосвязаны.

Вот что получилось:

🔹люди с высоким уровнем сопереживания чаще стремятся защищать голосового помощника, если кто-то ему грубит при них. А ещё всегда извиняются перед своими Алисами, если нагрубили сами. С этого вопроса, кстати, идея исследования и родилась.
Я всё хотела понять, что же в действиях Алисы заставляет людей защищать её — хотя очевидно же, что она, бесчувственная железяка, в защите не нуждается. Ну вот и ответ. Дело не в действиях Алисы, а в том, что люди — хорошие и сопереживающие котики. Теперь у меня есть данные, которые это подтверждают!

🔹внутриличностный и межличностный эмоциональный интеллект (уровень понимания своих чужих эмоций — соответственно), к моему удивлению, вообще особо не влияют на общение с голосовыми помощниками.

🔹зато большое значение имеет уровень контроля экспрессии — насколько хорошо человек контролирует проявление своих эмоций. Так вот. Людей, которые хорошо конролируют свои эмоции бесит, когда голосовой помощник выходит за рамки делового общения — шуточки там шутит, дерзит. А действительно, хули это Сири себе позволяет, если я — нет?!

🔹а ещё людей с высоким уровнем контроля экспресии тоже бесит, когда кто-то грубит голосовым помощникам при них. Но видимо, не потому что они такие сопереживающие, а потому что ожидают от людей того же, что делают сами — контроля [blyat’] своих [suka!] эмоций.

Ещё я немного посравнивала, как влияют на общение с голосовыми помощниками всякие там социально-демографические факторы: пол, возраст, наличие отношений:

🔹пол не влияет вообще никак.

🔹а вот отсутствие отношений/брака влияет: люди, у которых нет партнёра, чаще болтают с голосовыми помощниками просто так. Но испытывают стыд и неловкость, если железяка делает им комплименты [это очень логично, но всё равно от этих данных мне стало немного грустно]

🔹возраст тоже влияет: люди старше 35 чаще испытывают чувство вины перед голосовыми помощниками, если вдруг нагрубят им. И, наоборот, больше радуются, когда голосовой помощник говорит им комплименты, чем люди младше 35.

Такие дела. Надеюсь, было познавательно:)

[Передаю много больших спасибов всем 40 человекам, которые ответили на мою анкету.
И особенно большое спасибо Саше, который посчитал мне эти данные своим питоном
]
Моя жена на мой канальчик не подписана, пусть хоть так на него "посмотрит" 😊
#c #prog

Пост специально для Вафеля.

Преамбула:
У нас в кодах есть очередь. Она предназначена для работы из двух потоков: один поток пишет в очередь, другой из неё читает.
При этом в ней не используются никакие примитивы синхронизации. И вот не все верят в то, что это может работать гарантированно и безопасно.
Сейчас я покажу, что это за очередь и объясню как она работает, и почему это безопасно. Единственное, чтобы соблюсти все правовые аспекты, я рассмотрю не конкретно ту очередь, что используется у нас, а напишу вольную интерпретацию идеи на чистом Си.
К слову, идея очень просто и элегантна и очень мне нравится. Вполне вероятно, что этот код изначально был взят из книг (Александреску, или что-то подобное), но уверенности у меня в этом нет, только подозрение.

Так как переписываем на Си, и шаблонов тут нема, то в качестве данных будем использовать обычный int.
Итак, нам нужна очередь. Простейший способ оформить очередь - односвязный список. Так и сделаем:
struct Item {
int data;
struct Item* next;
};

Так же добавим для удобства методы для создания и удаления Item'а:
struct Item* new_item(int data) {
struct Item* item = (struct Item*)calloc(1, sizeof(struct Item));
item->data = data;
return item;
}
void free_item(struct Item* item) {
free(item);
}

Обращаю внимание, что мы не следим за корректностью выделения памяти и проверкой входных данных на NULL, ибо это всего лишь пример.

Что же, вроде бы очередь фактически готова, но пока не понятно, как оно может предоставлять гарантии безопасности и отсутствия гонок данных при конкурентном доступе. А всё потому, что чтобы магия заработала, нам потребуется ещё одна небольшая структурка:
struct Queue {
struct Item* head;
struct Item* tail;
}
struct Queue* queue_init() {
struct Queue* queue = (struct Queue*)malloc(sizeof(struct Queue));
struct Item* item = new_item(0);
queue->head = item;
queue->tail = item;
return queue;
}

В примере выше нет функции-деструктора, потому что в нём будет некоторая хитрость, и рассмотрим мы его позже.
Немного туп(л,)
#c #prog Пост специально для Вафеля. Преамбула: У нас в кодах есть очередь. Она предназначена для работы из двух потоков: один поток пишет в очередь, другой из неё читает. При этом в ней не используются никакие примитивы синхронизации. И вот не все верят…
Итак, мы уже имеем две структуры: одна представляет собой элемент очереди, вторая описывает "всю" очередь с помощью указателей на хвост и голову списка. Казалось бы, зачем в односвязном списке указатель на хвост, но ответ очень прост. И на самом деле это ответ на ещё один вопрос: "А как нам определить, пустая очередь или нет?" А вот так:
int queue_is_empty(struct Queue* queue) {
return queue->head == queue->tail;
}

Таким образом, уже понятна одна хитрость, используемая в этой очереди: мы используем один Item, как "заглушку". Этот элемент должен быть в очереди всегда и он всегда будет пустым, без валидных данных. Но именно благодаря этой заглушке, мы можем спокойно оперировать с очередью, не боясь словить data-race на операциях записи и чтения. Продемонстрирую:
void queue_push(struct Queue* queue, int data) {
struct Item* item = new_item(data); // (1)
queue->tail->next = item; // (2)
queue->tail = item; // (3)
}
bool queue_pop(struct Queue* queue, int* data) {
if (queue_is_empty(queue)) // (4)
return 0;

strut Item* old_head = queue->head; // (5)
queue->head = old_head->next; // (6)
free_item(old_head); // (7)

*data = queue->head->data; // (8)
return 1;
}

Всё. Наша очередь полностью готова. Весь код выше занял 42 (don't panic!) строки. И он красив и изящен, как мне кажется. Но если читатель со мной ещё не согласен, то объясню последний листинг:
Немного туп(л,)
Итак, мы уже имеем две структуры: одна представляет собой элемент очереди, вторая описывает "всю" очередь с помощью указателей на хвост и голову списка. Казалось бы, зачем в односвязном списке указатель на хвост, но ответ очень прост. И на самом деле это ответ…
(1) Мы создаём новый элемент в потоке писателя. В этом время поток-читатель может спокойно оперировать с очередью как хочет, ведь мы ещё не тронули память, которая "пошарена" между потоками. Кстати это может быть совершенно не атомарная операция, и если data будет посложнее, чем int, то всё равно ничего не изменится.
(2) Вот тут мы уже трогаем шареную память и де-факто изменяем хвост, сделав очередь длиннее на один элемент. Однако, посмотрим на (4) и вспомним, что, для проверки "пустоты" очереди, поля tail'а не используются от слова совсем. Используется значение самого указателя tail, а мы его ещё не меняли. То есть, несмотря на то, что мы уже вроде бы меняем общую для потоков память, на самом деле, мы меняем "невидимую" для потока-читателя память. То есть читатель может в этот же момент времени начал обрабатывать данные в tail, но мы ему не помешаем.
(3) А вот тут мы уже меняем указатель хвоста и наши добавленные данные становятся видимыми для потока-читателя.
(4) Здесь мы уже в потоке читателя проверяем очередь на пустоту, сравнивая указатели начала и конца очереди. Как я говорил в (2), мы можем начать обрабатывать данные в последнем элементе списка, пока писатель добавляет новые данные в нашу очередь (на самом деле это будет происходить в пунктах 5-8). Ещё возможен вариант, что head == tail, но писатель уже добавил данные в tail->next, но не успел изменить сам tail. Тогда всё тоже безопасно, мы просто вывалимся из функции. Ну а вариант, пока мы читаем далеко от хвоста - неинтересен.
(5) Тут мы берём нашу "старую" голову списка. Напомню (см. функцию queue_init), что первый элемент всегда заглушка. В нём нет валидных данных. Поэтому мы берём этот элемент, в (6) смещаем голову, чтобы она указывала на item с валидными данными и в (7) дропаем наш элемент-заглушку.
(8) А тут мы уже читаем валидные данные очереди и возвращаем их в вызывающий код (через указатель data). При этом, обращаю внимание на два момента:
1. Мы читаем из данных, которые не трогаются потоком-писателем. Он эту область сформировал ещё в (1), пока мы не имели сюда доступа, и больше не трогает этот участок памяти.
2. После того, как мы прочитали данные, эти данные становятся невалидными. Их нельзя читать второй раз. Будь здесь что-то сложнее, чем int, например указатель на сложную структуру, то стоило бы этот указатель занулить. Или если бы это была сама сложная структура (а не указатель на неё), то как-то её уничтожить. В любом случае это уже специфика конкретных данных, которые будут в этой очереди храниться. Если бы я приводил пример на C++ с шаблонным кодом, то да, я бы записал здесь, например queue->head->data = T {}, или что-то подобное.

А, ну и я забыл ещё кое-что, функцию уничтожения очереди (на самом деле я её забыл специально, иначе не получилось бы 42 строки):
void queue_free(struct Queue* queue) {
int tmp;
while(queue_pop(queue, &tmp));
free_item(queue->head);
free(queue);
}

Вот так, красиво и аккуратно мы обходим data races и memory corruption, если соблюдаются два инварианта:
1. Только один поток пишет.
2. Только один поток читает.
Ну и, исходя из написанного кода функции-деструктора: уничтожение очереди = чтение из очереди. Правда если вы пытаетесь уничтожить очередь и параллельно с ней работать (читать или писать - не важно), то вы делаете что-то неправильное.