LINUX.ORG.RU

Сообщения KivApple

 

Смешивание асинхронного и синхронного кода в Python

Форум — Development

Допустим, есть приложение на flask + socketio + eventlet. Внутри обработчика сообщения от socketio я могу писать какой-нибудь синхронный код. Например, сделать print или отправить ответ клиенту. Для конкретики вот примерный мой код:

import eventlet
import eventlet.wsgi
import socketio

from flask import Flask, render_template


sio = socketio.Server()
app = Flask(__name__)


@app.route('/')
def main():
    return render_template('main.html')


@sio.on('my-msg')
def handle_login(sid, data):
    print('my-msg: ' + str(data))


if __name__ == '__main__':
    app = socketio.Middleware(sio, app)
    eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

Однако, мне нужно работать с библиотекой, которая поддерживает только асинхронный стиль программирования. Не на базе eventlet или чего-то подобного, а на базе непосредственно yield from и т. п. Каким образом я могу делать к ней обращения внутри обработчика сообщения?

Пока приходит в голову, что нужно запустить в отдельном потоке eventloop от asyncio и запускать coroutine в нём при приходе сообщения. В свою очередь по окончании работы coroutine нужно как-то вызвать sio.emit (но это может быть не безопасно из-за того, что я делаю это из другого потока).

 ,

KivApple
()

c-oop-gen: ООП в Си

Форум — Development

Программирование в ООП-стиле на Си как правило порождает достаточно большое количество boiler plate кода. А итоговая программа пестрит приведениям типов. «Хватит это терпеть» решил я и запилил сие поделие: https://github.com/KivApple/c-oop-gen.

1) Вы описываете структуру классов (поля, методы, наследование) в XML-формате.

2) Вы генерируете два заголовочных файла из этого описания. Рекомендуется делать это не в ручную, а в качестве одного из шагов компиляции проекта.

3) Первый файл вы инклюдите всюду, где хотите использовать описанные классы. Второй файл вы инклюдите только в модуль с реализацией соответствующих классов (там описаны таблицы виртуальных методов).

4) PROFIT

Пример описания пакета классов:

<?xml version="1.0" encoding="utf-8" ?>
<package>
    <include file="stdlib.h"/>
    <class name="BaseObject">
        <method name="destroy" virtual="yes">
        </method>
    </class>
    <class name="DerivedObject" inherits="BaseObject">
        <field name="tmp" type="int"/>
        <method name="foo">
            <arg name="bar" type="int"/>
        </method>
        <method name="staticMethod" static="yes"/>
    </class>
</package>

После этого методы классов можно вызывать легко и просто:

DerivedObject_foo(another_object, 10);
DerivedObject_staticMethod();
DerivedObject_destory(another_object);
BaseObject_destroy(some_object);

Как можно заметить, при наследовании класса от другого, создаются обёртки для всех методов, которые не были переопределены, что позволяет отказаться от ручного приведения типов.

Поддерживаются виртуальные методы (как абстрактные, так и имеющие реализацию, к слову, переопределять в потомках можно только виртуальные методы), статические методы (не наследуются потомком, рекомендуется конструкторы делать на них) и обычные. Также любые методы могут получить переменное число аргументов с помощью атрибута «variadic».

Конструктор может быть любой функцией. Главное присвоить obj->vtable = &ClassName_vtable, если класс содержит хотя бы один виртуальный метод.

Деструкторы - это обычные виртуальные функции.

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

В отличии от многих других библиотек привносящих ООП в Си, сгенерированный код не требует НИЧЕГО (даже libc). Ведь это просто набор define'ов и структур.

Кстати, пакеты классов могут использовать друг-друга с помощью <include package=«some-package.xml»>. В этом случае можно будет наследоваться от классов объявленных в другом пакете (а сгенерированный исходник будет содержать #include «some-package.h»). При этом однако же генерировать заголовочный файл для подключенного пакета придётся отдельно.

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

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

 , , , ,

KivApple
()

Запуск процесса и Ctrl-C

Форум — Development

Я запускаю gdb из скрипта на Python:

gdb = ctx.get_param("TOOLCHAIN_PREFIX") + "gdb"
st_util = ctx.get_param("ST_UTIL_EXECUTABLE")
elf_name = ctx.get_param("EXECUTABLE_NAME")
st_util_process = subprocess.Popen([st_util])
subprocess.call([gdb, "-ex", "target remote :4242", elf_name])

Всё работает, но если внутри GDB нажать Ctrl-C, то комбинацию клавиш обработает не GDB, а Python, что проявится в виде завершения скрипта (вместе с GDB, разумеется).

Как можно запустить GDB из Python таким образом, чтобы Ctrl-C работал также как если запустить GDB обычным образом?

 ,

KivApple
()

Удаление виджета в PyQt5

Форум — Development

Допустим, есть QVBoxLayout, в который динамически добавляются кнопки:

labels = ['Button 1', 'Button 2', 'Button 3']
for label in labels:
    button = QPushButton(label)
    self.vbox.addWidget(button)

Это происходит внутри метода класса и по идее все переменные локальные, так что должны уничтожиться после выхода из него (если сделать button = None в конце, то ничего не меняется).

А затем в какой-то момент времени мы хотим удалить эти кнопки:

while self.target_buttonbox.takeAt(0):
    pass

В результате данного цикла кнопки удаляются лишь из layout'а, но не сами объекты. Это выглядит как видимые кнопки, которые больше не подчиняются layout. Если же делать не просто takeAt, а вызывать deleteLater к полученному takeAt виджету, то приложение крашится.

Как правильно удалить все виджеты окончательно?

 , ,

KivApple
()

CMake toolchain

Форум — Development

Имеется toolchain-файл для CMake, который определяет с помощью set переменные CMAKE_C_COMPILER и CMAKE_CXX_COMPILER. Проблема в том, что в некоторых IDE (как минимум Qt Creator и NetBeans, но не CLion или работа с проектом из командной строки) попытка открыть проект приводит к бесконечному циклу конфигурирования проекта, а затем вывода сообщения:

You have changed variables that require your cache to be deleted.
Configure will be re-run and you may have to reset some variables.
The following variables have changed:
CMAKE_C_COMPILER= /usr/bin/gcc
CMAKE_CXX_COMPILER= /usr/bin/g++

Вроде как файл тулчейна специально нужен, чтобы объявить пути к компиляторам при кросс-компиляции, что же я делаю не так?

 

KivApple
()

mcu-info-util

Форум — Talks

Как уже я здесь немного говорил, я разрабатываю архиполезную (на мой взгляд) утилиту mcu-info-util. Она OpenSource, написана на Python и нативно работает под Linux.

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

  • Находить компиляторы avr-gcc и arm-none-eabi (актуально для Windows, утилита ищет в реестре некоторые ключи, которые туда пишут установщики соответствующих тулчайнов, также производится поиск в PATH, для Linux только поиск PATH).
  • Подсказывать ключи, которые нужно передать компилятору и линковщику, чтобы успешно скомпилировать проект под целевой микроконтроллер (и если для AVR это всего лишь -mmcu=..., то для ARM всевозможные указания версии cortex, наличия модуля аппаратной математики и т. д.).
  • Генерировать скрипт линковки для ARM. Больше не нужно писать свой скрипт или искать готовый - достаточно названия микроконтроллера (например, STM32F103C8T6) и утилита создаст подходящий скрипт линковки.
  • Генерировать заголовочный файл с описанием регистров периферии выбранного микроконтроллера (в настоящий момент только для ARM, используется информация из файлов SVD, включённых в комплект поставки). Не нужно при использовании libopencm3 или CMSIS, но никто и не заставляет использовать.

Например, мы хотим собрать прошивку под микроконтроллер STM32F103C8T6:

$ mcu-info-util --mcu stm32f103c8t6 --find-compiler
/usr/bin/arm-none-eabi-gcc
$ mcu-info-util --mcu stm32f103c8t6 --print-flags
-D_ROM=65536 -D_RAM=20480 -D_ROM_OFF=0x08000000 -D_RAM_OFF=0x20000000 -mcpu=cortex-m3 -mthumb -DSTM32F1 -msoft-float
$ mcu-info-util --mcu stm32f103c8t6 --linker-script script.ld
$ mcu-info-util --mcu stm32f103c8t6 --header mcudefs.h

Разумеется, было бы разумно использовать mcu-info-util не самостоятельно, а внутри скриптов сборки. На этот случай я приготовил пару примеров - для make и для cmake в каталоге misc репозитория проекта.

Таким образом Makefile проекта может выглядеть как-то так (разумеется, проект будет поддерживать инкрементальную компиляцию с отслеживанием зависимостей исходных файлов) - https://github.com/KivApple/mcu-info-util/blob/master/misc/makefile-project/M...

А проект CMake как-то так - https://github.com/KivApple/mcu-info-util/blob/master/misc/cmake-project/CMak....

Выбор используемого микроконтроллера осуществляется всего лишь одной переменной - MCU. Скрипты произведут серию обращений к mcu-info-util, в итоге будет найден (если установлен) необходимый компилятор, флаги компиляции и при необходимости сгенерирован скрипт линковщика и заголовочный файл с описаниями регистров.

Согласитесь, это гораздо удобнее хардкода размеров ОЗУ и ПЗУ, путей к компилятору (в настройках IDE) и т. д. Функционал подобного уровня (выбор МК по названию и автонастройка проекта под него) предоставляют лишь коммерческие IDE, а моё решение не имеет каких-либо привязок. Вы можете использовать в своём проекте любые библиотеки (скажем, подключить исходники какой-нибудь RTOS), писать код в любой IDE (нужна лишь поддержка Makefile или CMake, либо возможность скриптовать систему сборки и прямые руки), данная утилита лишь берёт на себя необходимую рутину по выбору необходимого компилятора и флагов компиляции без которых проект банально не заработает.

В настоящий момент имеется поддержка только микроконтроллеров STM32 и AVR (также теоретически может нормально заработать для ARM от Atmel).

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

 ,

KivApple
()

CMake - экранирование вывода команды

Форум — Development

Допустим, я вызываю какую-нибудь команду с помощью execute_process и заношу её результат в переменную с помощью OUTPUT_VARIABLE:

execute_process(COMMAND some-command OUTPUT_VARIABLE my_var OUTPUT_STRIP_TRAILING_WHITESPACE)

Если вывод some-command содержит символ обратного слеша, то любое последующее использование ${my_var} вызывает ошибку, потому что, судя по всему, CMake воспринимает это как экранирование следующего символа.

Как можно вызвать команду таким образом, чтобы её результат корректно занёсся в переменную с заменой всех \ на \\?

 

KivApple
()

Лицензии на SVD

Форум — Talks

SVD - это описание микроконтроллера (всей его периферии со списком регистров, прерываний и т. д.) в XML-формате. Широко применяется как минимум для ARM-чипов. Скачать можно отсюда (для STM32, на этом же ресурсе есть SVD от других производителей): https://cmsis.arm.com/vendor/stmicroelectronics/

Я разрабатываю утилиту mcu-info-util, которая делает всякие полезные вещи. Например, создаёт скрипт линковки или заголовочный файл с описаниями регистров периферии. Последнее я делаю как раз на основании SVD. В настоящий момент я тупо включил необходимые файлы в репозиторий с исходным кодом проекта (например, https://github.com/KivApple/mcu-info-util/tree/master/mcu_info_util/metadata/...).

Лицензия на SVD для STM32, например (лицензии для других SVD повторяются практически дословно): https://github.com/KivApple/mcu-info-util/blob/master/mcu_info_util/metadata/....

Итак, собственно меня интересует правильно ли я поступаю.

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

Однако в связи с этим два вопроса:

1) Могу ли я держать эти SVD в своём репозитории при условии сохранения всех уведомлений о копирайте и добавления в README указания, что «компоненты в каталоге metadata имеют различные лицензии, приложенные к каждому компоненту»? Или же я должен нагенерировать заголовочных файлов из каждого SVD и распространять только их?

2) Нужно ли добавлять в сгенерированные файлы какое-либо уведомление? Если да, то какое.

 

KivApple
()

Lisp: передача параметров следующему методу

Форум — Development

Хочу переопределить стандартный конструктор объекта, чтобы он принимал дополнительный параметр, который обрабатывался специальным образом, а оставшиеся параметры и новый передавались родительскому методу.

(defmethod initialize-instance :around ((object my-class) &rest args &key my-param)
  (call-next-method object :another-param my-param args)))

Что-то типа такого (над my-param могут произойти какие-то операции, мне нужно не просто переименовать один key-параметр в другой).

Проблема находится в последней строчке - мне нужно как-то передать args не в качестве аргумента, а чтобы функция была вызвана с этим списком аргументов.

Можно сделать что-то подобное:

(defmethod initialize-instance :around ((object my-class) &rest args &key my-param)
  (apply 'call-next-method (cons object (cons :another-param (cons my-param args)))))

С обычной функцией такое-бы прокатило, но call-next-method судя по всему является макросом и поэтому я получаю ошибку, мол функция не найдена (хотя прямой вызов работает нормально).

Каким образом в Lisp можно заставить функцию скушать список, как если бы это были её прямые аргументы, а не один аргумент со значением списка?

 ,

KivApple
()

Возврат lambda из функции

Форум — Development

В рамках изучения Lisp я решил один небольшой проект веб-приложения сделать на нём (ибо заказчику всё равно какой язык я буду использовать, ему лишь бы работало, да и время у меня есть).

В качестве веб-сервера использую модуль Hunchentoot. Поскольку мне не нужно ничего супер-сложного, использую его фреймворк easy-handlers для задания маршрутов.

Хочу сделать так, чтобы обработчик вызывался для любого запроса, начинающегося с «/foo/». То есть сюда подходят всякие варианты «/foo/bar», «/foo/baz» и т. д.

У функции define-easy-handler параметр :url может быть не только строкой, но и функцией.

Таким образом можно написать что-то вроде:

(defun starts-with (substr str)
  (if (>= (length str) (length substr))
    (equal substr (subseq str 0 (length substr)))))

(define-easy-handler (foo-page :uri #'(lambda (request) (starts-with "/foo/" (request-uri request)))) ()
  ...)

Но это же не красиво! Хочу вынести создание лябмды в отдельную функцию. Чтобы писать как-то так:

(defun prefix-uri (prefix)
  `(function (lambda (request) (starts-with ,prefix (request-uri request)))))

(define-easy-handler (foo-page :url (prefix-uri "/foo/"))
  ...)

Однако как бы я не расставлял квотирование, либо получаю ошибку компиляции, либо сервер выдаёт 404, либо при обращении к странице вылетает ошибка 500, а в логах пишется, мол мне требуется function symbol, а ты подсунул мне какую-то фигню вместо него.

Как правильно такое реализовать?

 ,

KivApple
()

Взаимоотношения методов классов и пакетов в Lisp

Форум — Development

Допустим, есть следующий код:

(defpackage :foo
  (:use :common-lisp)
  (:export x))
(defpackage :bar
  (:use :common-lisp :foo))

(in-package :foo)

(defgeneric m (arg))

(defun x (obj) (m obj))

(in-package :bar)

(defclass my-class () ())

(defmethod m ((obj my-class)) (print "Ok"))

(defvar obj (make-instance 'my-class))

(m obj)
(x obj)

То есть у нас есть пакет foo с функцией x, которая выполняет некоторые действия с переданным объектом, вызывая его метод m. При этом я бы хотел использовать принцип утиной типизации - если у любого объекта есть метод m, значит он умеет делать то, что нужно функции x.

Проблема возникает, если класс переданного объекта описан в другом пакете, который не импортирован в foo (в примере выше класс находится в пакете bar). SBCL ругается:

Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                    {10029365E3}>:
  There is no applicable method for the generic function
    #<STANDARD-GENERIC-FUNCTION FOO::M (0)>
  when called with arguments
    (#<MY-CLASS {1002A00803}>).

Как эту проблему следует решать?

 ,

KivApple
()

Моя первая программа на Lisp

Форум — Development

Все везде нахваливают Lisp и я решил тоже потыкать его. Почитал немного документации, а затем написал свою первую программу.

http://pastebin.com/CZ6MTw1S

Это преобразователь из инфиксной в префиксную форму. С учётом скобок и приоритетов операций. А ещё с возможностью вызывать функции помимо выполнения математических операций.

То есть пишем что-то вроде (c-expr ( 2 + 2 * 2 + sin ( 1 ) ) / 2 ), а оно преобразует это в нормальную лисповую форму записи - (/ (+ (+ 2 (* 2 2)) (sin 1)) 2), а такое уже легко вычисляется средствами самого Lisp.

Программа работает вроде как и всегда (пробовал несколько тестов) выдаёт верный результат, но вот вопрос - там огромное количество всяких ветвлений и рекурсий. Насколько я рационально её написал, не будет ли проблем с производительностью (быть может, надо как-то оптимизировать)?

Просто в Lisp не принято писать так, как в обычных языках программирования (да и некоторые конструкции при дословном переводе будут некрасиво смотреться), поэтому и советы по оптимизации от них не годятся. Например, если сохранять в локальные переменные все значения, которые используются дважды (чтобы не считать их 2 раза), то будет дикая лапша из let. Или это не нужно? Или это не лапша (и вообще мой код не лучше), а норма?

 ,

KivApple
()

Вопрос про Lex/Yacc

Форум — Development

Вообще, я использую PLY, но поскольку синтаксис описания грамматики он использует от Yacc, то вопрос скорее по нему, а не по питоноспецифичным вещам.

На самом деле моя грамматика гораздо больше, но чтобы не загромождать сообщение написал minimal not working example:

from ply import lex, yacc

tokens = ('LT', 'LPAREN', 'RPAREN', 'NAME')

t_NAME = r'[A-Za-z_@][A-Za-z0-9_@]*'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LT = r'<'

t_ignore = ' '

precedence = (
	('left', 'LT'),
	('left', 'CALL')
)

def p_expression_binop(p):
	'expression : expression LT expression'
	p[0] = (p[2], p[1], p[3])

def p_expression_call(p):
	'expression : expression LPAREN RPAREN %prec CALL'
	p[0] = ('Call', p[1])

def p_expression_name(p):
	'expression : NAME'
	p[0] = ('Name', p[1])

lexer = lex.lex()
parser = yacc.yacc()
print(parser.parse('a < b()'))

Если выполнить этот скрипт (PLY должен быть установлен через пакетный менеджер дистрибутива или pip), то можно получить результат (добавил отступы и переносы для читабельности):

[
    ('Call',
        ('<',
            ('Name', 'a'),
            ('Name', 'b')
        )
    )
]

Как можно заметить, операция сравнения оказалась более приоритетной (хотя в precedence она идёт раньше, что даёт меньший приоритет - проверено на другой грамматике, где есть сложение и умножение - 2 + 2 * 2 вычисляется с верными приоритетами), чем вызов функции и в результате мы получаем вызов как функции результата сравнения переменной (a) и функции (b), что, конечно же, полная чушь.

Где я допустил ошибку и как её исправить?

 , ply, ,

KivApple
()

Библиотека парсинга для Python

Форум — Development

Чем нынче принято парсить тексты программ (а не HTML) на Python? Пока остановился на выборе между PLY и PyParsing. Какие достоинства и недостатки имеет каждая из них? Или, быть может, я не знаю о какой-то третьей библиотеке, которая лучше обеих?

Интересует удобство описания грамматики и скорость парсинга.

 ,

KivApple
()

STM32 I2C

Форум — Science & Engineering

Написал вот такой класс для работы с модулем I2C на STM32:

https://github.com/KivApple/ControllerFramework/blob/a07c28b93f160b2f1fddaf07...

https://github.com/KivApple/ControllerFramework/blob/a07c28b93f160b2f1fddaf07...

Используются примитивы BinarySemaphore и Mutex от FreeRTOS (простейшие C++ обёртки), поэтому вряд ли проблема в них.

Для теста общаюсь с MPU6050. Делаю следующие операции:

Прочитать регистр 0x75 (по факту отправка 1 байта, а потом приём 1 байта)
Записать 0 регистр в регистр 0x6B (по факту отправка 2 байт)
Прочитать 16-битный регистр 0x3B (по факту отправка 1 байта и приём 2 байт)
Подождать полсекунды и повторить

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

Мне такой подход не нравится, потому что я не смогу добавить поддержку режима slave (модуль же отключен всё время, пока не передаёт с режиме master). Что я делаю не так?

 ,

KivApple
()

Стриминг MJPEG

Форум — Multimedia

Создал с помощью mkfifo video.jpg. Моя программа, использующая OpenCV, сохраняет туда постоянно кадры обработанного видео с камеры с помощью imwrite(«video.jpg», frame). Запустил следующую команду (192.168.0.103 - адрес моего компьютера, на котором я хочу смотреть видео, а вообще вся эта штука крутится на одноплатнике):

tail -f video.jpg | ffmpeg -i pipe: -f mjpeg -r 15 -vcodec mjpeg udp://192.168.0.103:1234

Затем на своём компьютере запускаю:

ffplay -f mjpeg udp://192.168.0.255:1234

И смотрю видео. Всё хорошо, но не устраивает задержка (где-то полсекунды) и качество.

Насколько я понимаю, главная проблема в том, что ffmpeg зачем-то перекодирует кадры из JPEG в JPEG, чем портит качество и зря тратит ресурсы. Пробую добавить -vcodec copy, но тогда получаю кучу ошибок вида

[mjpeg @ 0x7f750fc0] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 11 >= 11

ЧЯДНТ? Хочу использовать JPEG, потому что ресурсов на одноплатнике мало и кодировать какой-нибудь MPEG ему будет тяжело, а вот с пропускной способностью сети особо проблем нет. Хочу использовать UDP или что-то подобное, чтобы кадры бились и терялись, но видео продолжало идти, даже если с качеством связи будет не всё в порядке, ибо минимальные задержки превыше сохранности кадров.

 , ,

KivApple
()

И снова про многопоточность

Форум — Development

Сделал вот такой класс двунаправленного связанного списка http://pastebin.com/JRsWwaKS. По понятным причинам он не является thread-safe. Поэтому я любые обращения к нему защищаю с помощью pthread mutex. И всегда думал - «если есть потоконебезопасный код, то защищаешь его mutex, теряешь какую-то производительность, зато работаешь как обычно». А вот нифига.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
List list;

void* thread_func(void* arg) {
	ListItem item;
	while (true) {
		pthread_mutex_lock(&mutex);
		list.insertHead(&item);
		pthread_mutex_unlock(&mutex);
	}
}

int main() {
	pthread_t threads[2];
	pthread_create(&threads[0], nullptr, thread_func, nullptr);
	pthread_create(&threads[1], nullptr, thread_func, nullptr);
	while (true) {
		pthread_mutex_lock(&mutex);
		ListItem* item;
		do {
			item = list.removeTail();
		} while (item);
		pthread_mutex_unlock(&mutex);
	}
	return 0;
}

Это небольшой тестовый код. Два потока добавляют элементы в список (добавление одного и того же элемента допустимо, ведь в функциях добавления есть проверка, что элемента ещё нет в списке и они просто вернут false). А другой извлекает все доступные элементы. Пример абстрактный, в реальном приложении над элементами списка ещё и совершают всякие полезные действия (а ещё иногда спят и т. д.). Почему mutex не интегрирован в класс списка? Потому что опять же в реальном коде списки являются частями более сложных структур, которые уже имеют свои mutex. Это чтобы избежать стилистических замечаний. Вышеприведённый код 100% блокирует все операции со списком, хотя и делает это не в ООП стиле.

И в итоге что я получаю? А получаю я срабатывание assert(retval) в функции List::removeTail. А это может означать лишь одно - в списке числится (и не просто числится, а числится хвостом списка) элемент, у которого m_list == nullptr. Что и подтверждается, если посмотреть отладчиком после abort из-за проваленного assert. Значит либо ошибка в реализации алгоритма списка (но в однопоточных тестах вроде проблем не было), либо я что-то делаю не так с блокировками.

Я понимаю, что скорее всего у меня возникло некоторое непонимание с тем как писать многопоточные приложения на C++ и считаю, что данный пример позволит наконец-то во всём разобраться (ибо он не сложный, кода мало и я могу легко сделать минимально рабочий пример). А ещё я собираюсь читать книгу про многопоточное программирование в C++, однако хотелось бы получить первые замечания быстрее, чем я её прочитаю.

P. S.: Тестовый код компилирую без каких-либо оптимизаций.

UPD: Посыпаю голову пеплом. Ошибка была в том что функции добавления таки не проверяли проверку item->m_list. Сейчас всё работает.

 ,

KivApple
()

Вызов функции и барьеры памяти

Форум — Development

Допустим, мы пишем код для микроконтроллера. У нас есть две функции - enterCriticalSection и leaveCriticalSection. Они запрещают и разрешают прерывания соответственно. При этом делают это по-умному - используя счётчик таким образом, чтобы позволить рекурсивные блокировки. То есть если вызвать два раза enterCriticalSection, то прерывания будут разрешены только лишь после второго вызова leaveCriticalSection.

Теперь допустим, что у нас есть некоторая переменная, с которой мы работаем в двух местах - в обработчике прерываний и в этой «критической секции» (в обработчике прерываний во время работы с переменной мы тоже входим в критическую секцию, на всякий пожарный).

Мы уверены, что пока мы в обработчике прерывания, нового такого же прерывания не будет. Также мы уверены, что пока мы в критической секции, прерывание не выполнится. Поэтому мы не используем volatile при декларации переменной, чтобы зря не терять производительность.

Сегодня я столкнулся с тем, что судя по всему компилятор таки оптимизировал обращения к этой переменной сильнее, чем надо. Точнее, я могу сделать такой вывод по косвенным признакам - портилась структура связанного списка (добавление/удаление элементов туда как раз и осуществляется в критической секции). Как только я описал его поля как volatile - структура портится перестала.

Функции входа и выхода выглядят так:

void enterCriticalSection() {
	__asm__ volatile("cpsid i");
	counter++;
}

void leaveCriticalSection() {
	counter--;
	if (counter == 0) {
		__asm__ volatile("cpsid i");
	}
}

Функции вызывались из той же единицы трансляции, так что вполне могли заинлайнится.

Мог ли компилятор посчитать asm volatile(...) недостаточным условием для того, чтобы не использовать закешированные значения до его вызова?

Вставил после запрета прерываний и перед их разрешением asm volatile("":::«memory») и убрал volatile в декларациях переменных. Результат тот же - всё отлично работает.

Хорошо. А как насчёт вызова данных функций из других единиц трансляции, когда компилятор не будет знать, что внутри них есть asm("":::«memory»)? Он ведь догадается, что при вызове функции из другой единицы трансляции со значениями в памяти может случиться всё что угодно? Или, быть может, её надо как-то пометить для этого?

Я задаю эти вопросы в двумя целями:

1) Убедиться, что проблема скорее всего была именно в этом, а это не просто совпадение и на самом деле ошибка осталась.

2) Убедиться, что при вызове enterCriticalSection из другого модуля, всё будет работать так же хорошо.

 ,

KivApple
()

Assertion failed при работе с pthread

Форум — Development

Иногда (под отладчиком реже, без него чаще) во время одного из вызовов pthread_mutex_lock моя программа падает, выдав на консоль:

tpp.c:84: __pthread_tpp_change_priority: Assertion `new_prio == -1 || (new_prio >= fifo_min_prio && new_prio <= fifo_max_prio)' failed.

В какую сторону вообще копать? При какой ошибке вообще может такое происходить?

 

KivApple
()

Таймаут чтения из последовательного порта

Форум — Development

Открываю последовательный порт под Linux. Как-то так:

int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
fcntl(fd, F_SETFL, 0);
termios options;
tcgetattr(fd, &options);
options.c_cflag |= CLOCAL | CREAD;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_oflag &= ~OPOST;
options.c_cflag &= ~CRTSCTS;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~PARENB;
cfsetspeed(&options, B115200);
options.c_cc[VMIN] = 10;
options.c_cc[VTIME] = 20;
tcsetattr(fd, TCSAFLUSH, &options);
char buffer[10];
int count = read(fd, buffer, sizeof(buffer));
printf("count = %i\n", count);

Я ожидаю, что при отсутствии символов, приходящих через последовательный порт, программа завершится через 2 секунды (установленное значение c_cc[VTIME]), однако она зависает в бесконечном ожидании.

Если же убрать вызов fcntl, то read мгновенно возвращает 0 байт, но я то хочу, чтобы он подождал.

ЧЯДНТ?

P. S.: Я знаю про select, однако он не позволяет задать, сколько именно байт я хочу получить через порт - ожидание прервётся даже если придёт 1 байт, а в данном примере я жду сразу 10.

P. P. S.: Я работаю с виртуальным COM-портом, созданным микросхемой FT232RL.

 ,

KivApple
()

RSS подписка на новые темы