catpad: (Default)
[personal profile] catpad

Мы тут на работе организовали доморощенный курс по С++ и вообще программированию для людей без всякого компьютерного образования, которые хотят продвигаться.
Я подвизался его вести. 



15 занятий уже прошло, всё более-менее нормально, и вот дошли до multithreading.  И тут их прямо замкнуло. То есть, они понимают, что всё происходит одновременно, тут проблем нет. Но вот дошли до места, где один  thread передает другому данные через BlockingQueue. Сначала они никак не могли понять, что функции одного и того же класса, могут бежать в разных threads. Наконец, после долгих раздумий, одна девушка говорит: а зачем вообще нужна эта очередь? Почему нам просто не бросить данные из одного thread в другой?
И это меня прямо-таки ввело в просветление. Это же просто коан, если задуматься. Хлопок одной ладони. Думал-думал, и говорю — но мы же ровно это и делаем с помощью очереди — передаём данные из одного thread другому! Но она этот ответ не приняла (и я сильно подозреваю, что и все остальные тоже). Зачем нужен промежуточный этап (очередь какая-то), если можно просто послать вот эту штуку (instance) вот оттуда вот сюда?

Тут я совсем надолго задумался, и в принципе, думаю до сих пор. По всей видимости, проблема в том, что люди, далёкие от этого дела, не понимают разницы между data и execution. Я попробовал объяснить это так: вот смотрите, thread — это инструкции, которые не то чтобы живут в CPU, а исполняются там одна за другой, а данные — это то, что живет в памяти, и не исполняется, а просто существует. Дело усложняется тем, что инструкции работают не просто так, а с этими самыми данным, при этом сами инструкции (не процесс их исполнения, а их материальное воплощение, запись о них, тоже живёт в памяти, просто в другом месте — ну, это ладно).  
Кстати, пока писал, подумал: дело ещё усложняется и тем, что если смотреть на source code, то там действительно и переменные, и код функций, всё перепутано — какие-то функции в одном классе, какие-то в другом, при этом всё это может быть в разных threads без всякой системы. Какие-то переменные — члены одного класса, но изменяются из разных threads, другие принадлежат разным классам, но изменяются из одного; какие-то функции запускают новые threads — и так далее, до бесконечности. 


В общем, вопрос получился чисто философский. В обычном мире вроде бы нет такой проблемы: все более или менее понимают разницу между объектами мира и процессами, которые выполняются во времени и работают с этими объектами. А в компьютере почему-то у людей случился сбой — тот же принцип оказался непонятным. Почему так, не знаю (хотя подозреваю, что правильный ответ в последней мысли предыдущего абзаца). 




Date: 2020-07-19 12:13 am (UTC)
From: [identity profile] catpad.livejournal.com
Вот я читал ваш комментарий и прямо таял на глазах :)
Вы не поверите, насколько точно у нас совпадают мысли. Я тоже считаю, что это огромное зло, что людям часто преподают какие-то языки и при этом вообще ничего не объясняют, как это всё работает внутри. Особенно бессмысленно преподавать С++ и не показывать, что происходит в памяти.

Я встречал людей, которые программируют на С++ (и в общем, хорошо его знают), но не понимают, чем heap allocation отличается от stack allocation и что, например, если у тебя очень большой статический (или просто локальный) массив внутри функции, то это не повлияет на быстроту вызова функции. Или если ты хочешь менять поинтер внутри функции, то его надо передавать by reference (аргумент был: ну как - это же уже поинтер, зачем его by reference?)

Поэтому два самых первых занятия (4 часа, между прочим) я посвятил только разбору того, что происходит на стеке при вызове функций с разными типами аргументов (by value, by ref, by pointer); где и сколько живут локальные переменные; где хранятся разные типы данных в памяти; что есть program counter и как это всё связано. Потом ещё дал им домашнее задание нарисовать каждое изменение стека и все вообще все stack frames при работе рекурсивной функции factorial(3).

Но, как видите, всё равно не помогло. Хотя, я понимаю, что без этого всё было бы ещё гораздо хуже. По крайней мере, они теперь свободно оперируют понятиями scope, lifetime, heap и т.п.

Date: 2020-07-19 04:13 am (UTC)
From: [identity profile] yatur.livejournal.com
> если у тебя очень большой статический (или просто локальный) массив внутри функции, то это не повлияет на быстроту вызова функции

Ну, это вопрос философский. Если статический - не повлияет, я если локальный, да еще и проинициализирован, то надо же страницы виртуальной памяти выделять и заполнять их чем-то. Если ОЧЕНЬ большой - это займет время. А если ОЧЕНЬ-ОЧЕНЬ большой, то тупо стек кончится и время выполнения функции станет очень маленьким :)

Date: 2020-07-19 04:38 am (UTC)
From: [identity profile] catpad.livejournal.com
Ха-ха, ну, так далеко мы не заходили, конечно.
Я им показывал пример именно static, так что с этой точки зрения всё чисто.
Не думаю, что им вообще нужно знать про виртуальную память.

Date: 2020-07-19 02:20 pm (UTC)
From: [identity profile] yatur.livejournal.com
Да, пожалуй. Если начинать впихивать всё сразу, в голове образуется каша.

Date: 2020-07-20 08:32 am (UTC)
ak_47: (default)
From: [personal profile] ak_47
Даже самое хорошее объяснение не сразу усваивается. Но это дело практики и времени. Даже если человек не запомнит все нюансы, то хотя бы будет знать где копать если понадобится.

У меня вообще сложилось такое убеждение, что языки стоит преподавать где-то в самом конце обучения на CS. Как у врачей специализация. А до этого то, что общее между всеми языками.
Page generated Feb. 6th, 2026 08:48 am
Powered by Dreamwidth Studios