Pull to refresh

Comments 69

Спасибо, третий вариант очень красиво иллюстрирует, как может получиться 2.
В русскоязычной литературе threads всё же принято называть потоками. Нити — это fibers.
Мне кажется, fibers — это волокна.
Дело в том, что я читал некоторую литературу по устройству ОС, там также использовали термин «нить». Ещё я вчера прочитал на ВП вот это, что и стало окончательным решением по выбору термина.
Спасибо за замечание. Про волокна не знал.
Я сталкиваюсь с тем что threads называют нитями или потоками. Fibers — волокна.
Для определенности в Pascal объектные типы когда-то стали называть классами, в отличие от объектов-экземпляров. То же самое, поддерживаю в русском языке: Thread-ы — нити, Stream-ы — потоки.
Самый адекватный перевод на русский для thread — путь или маршрут. Отдельные пути или маршруты выполнения команд процессором, которые могут пересекаться, а команды могут использовать общую память. Но увы, устоялись действительно потоки.

А называть потоки нитями, лишь потому что таков словарный перевод — просто глупо.
UFO just landed and posted this here
На собеседовании в ИТ-компании было предложено ответить на следующий вопрос.

Какое значение будет содержать переменная counter по завершении работы обоих нитей и почему?
UFO just landed and posted this here
Отвечать нужно по варианту №1 и не тратить собственное время и время интервьюера.
я бы согласился, если бы речь шла о приеме на работу в research отделы ibm, sun, microsoft, etc, однако практик (инженер) должен знать правильные методы.
Еще неплохо бы понимать ПОЧЕМУ правильные методы лучше неправильных.
ответ зависит еще и от языка и платформы, ++ — может быть атомарным
на x86 он скорее всего и будет атомарным, если ++ оттранслируется в инструкцию inc которая как известно в качестве аргумента может принимать ссылку на область памяти
Если код будет запущен на многопроцессорной или многоядерной системе, то инкремент будет неатомарной операцией из-за наличия процессорных кэшей.
… при условии, что нити будут расбросаны по разным ядрам.
То есть тут два условия должно сработать независимых.
«скорее всего» — штука опасная Ж)
это тестовое задание, и его цель — узнать, можете ли вы мыслить «широко» если ответ 20 — значит человек незнает про «гонки» если ответ — хз сколько, надо использовать семафоры — человек мыслит по шаблону
>хз сколько, надо использовать семафоры — человек мыслит по шаблону
Зачем впадать в рассуждалки когда есть простой и лаконичный ответ — Undefined behavior. Чтобы проверить логику человека есть совершенно другие вопросы…
В данном случае шаблон не есть зло. В данном случае и нужно действовать по шаблону. Можно сколько угодно рассуждать о компиляторах и процессорах, поражая всех до глубины души, но программировать нужно с семафорами (мьютексами, критическими секциями).

Лично я бы человека, ответившего «20» не взял бы на работе (ну вообще чувак не в теме).
Но и людей, ответивших по вариантам 2 и 3 не взял бы тоже — они склонны к вот таким вот, узко-платформенным, вероятностным и ненадежным рассуждениям и будут писать такой же код. А человека, сказавшего «хз, это муть какая-то, надо написать вот так и вот так» — взял бы.
Собственно вариант 3 и есть «хз сколько». Только еще с пояснениями почему именно «хз».
Совершенно верно. Поэтому в задании и не было сказано о конкретных условиях. Оно больше рассчитано на проверку знаний о работе процессора и многопоточности. А также на возможность мыслить логически и, наверное даже, фантазировать (:
При 1280*1024 не виден правый край таблицы. Поправьте, пожалуйста (например, картинкой с превьюшкой).
Добавил для вас вариант таблицы в виде картинки. Ссылку найдёте сразу под таблицей. (У меня разрешение такое же, я просто уменьшил кегль.)
Результатом такого кода должно быть увольнение написавшего.
Это уже собеседование не программиста, а проджект-менеджера.
static поля класса должны инициализироваться в первую очередь после загрузки класса.
Если существует два потока, каждый из которых инкрементит статическую переменную на +10, то резт-т должен быть 20.
Можете объяснить, в каком случае возможет вариант, отличный от 20?

Да и вопрос сам по себе неграмотный. Не указан ни язык, ни платформа.
Это точно было собеседование, а н есоревнование по риторике?
Уже малёхо поднадоели тут на хабре эти задачи на параллельные потоки без объектов синхронизации. Уж топик пятый на моей памяти об этом. И комменты все по стандартному кругу: «так писать неверно...», «уволить программера...», «а я вот знаю как работает процессор и и на выходе может быть число N, а может и не быть» и т.д.
IMHO, область решения: 1..20.
Атрибут volatile внес бы определенность.
volatile всего лишь не позволил бы компилятору соптимизировать цикл, однако не запретил бы одновременный доступ из двух нитей.
Попробуйте для полноты картины написать код и скомпилить его в VS, Intel C++ и например gcc и потом объясните разницу :)
при разных запусках одно и тогоже скомпилированного кода возможен разный вариант. автор просто указывает на очевидную проблему.
UFO just landed and posted this here
Ваш пример про файлы совсем не в тему. Тут всё таки статическая переменная. По идее (в C#) использование статических переменных потокобезопасно. И я бы ответил 20
при чем тут статические? в дотнете нет гарантии атомарности инкремента.
может я не догоняю. Ну допустим, один поток взял переменную, а второй получается будет ждать? Как неатомарность инкремента может повлиять на конечный результат? Пока один поток увеличивает на единицу, второй в это время тоже может увеличить на единицу? Ну и в итоге, как я понимаю, переменная увеличится на 2
3-й вариант в статье, инкремент — три операции: чтение, увеличение, запись. пока один увеличевает на единицу, второй увеличивает на единицу то-же, что и первый
хотя я понял о чем Вы:
т.е. ++ развернется в counter = counter + 1;
Получается, два потока возьмут одинаковое значение counter.
угу, сразу не заметил
UFO just landed and posted this here
нужно объяснить тому, кто принимает интервью, что в данном коде ошибка и результат, в общем случае, не предсказуем. думаю любому более/менее опытному системному программисту данный код режет глаз.
Уже распечатал. Теперь куда идти на собеседование?
> Какое значение будет содержать переменная counter по завершении работы обоих нитей и почему?

Правильно будет не «обоих нитей», а ОБЕИХ нитей…
Забавное упражнение. Другое дело будет ли ситуация п3 возникать в реальности хотя когда либо.
Правильный вопрос — «когда такая ситуация возникать не будет?».
Я бы наверно про себя подумал «что за фигня?» Но ответил бы скорее всего 20 :)
Чтобы не отпугнуть возможного «умного» собеседуемого от Вашей логики и подстегнуть к рассуждениям, я бы не стал так категорично формулировать задание:

> Какое значение будет содержать переменная counter по завершении работы обеих нитей и почему?

Вместо «будет» я бы использовал более нейтральное «может». Этим Вы задание не упростите (те, кто понимает проблему, грамотно ее ответят), но открываете дорогу к рассуждениями, а не к однозначному ответу на Ваш вопрос.
К сожалению, большинство известных мне тестов на разные квалификации — это закрытые тесты с определенным (одним) правильным вариантом ответа, и встречая такую задачу в резюме трудно отделить — где нужно проявить мышление, а где попытаться просто дать один ответ.
Достойное замечание, спасибо.
#include <stdio.h>
#include <pthread.h>

static int counter;

void worker()
{
        int i;
        for (i = 1; i <= 10; i++)
                counter++;
}

void* f(void *p)
{
        worker();
}

int main()
{
        int i, r[22] = {};

        for(i = 0; i < 1000000 && counter != 2; ++i)
        {
                pthread_t t1, t2;
                counter = 0;
                pthread_create(&t1, NULL, f, NULL);
                pthread_create(&t2, NULL, f, NULL);
                pthread_join(t1, NULL);
                pthread_join(t2, NULL);
                ++r[counter];
        }
        for(i = 0; i < 22; ++i)
                printf("%d:\t%d\n", i, r[i]);
}


Цикл превратился в это:
worker:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movl    $1, -4(%rbp)
        jmp     .L2
.L3:
        movl    counter(%rip), %eax
        addl    $1, %eax
        movl    %eax, counter(%rip)
        addl    $1, -4(%rbp)
.L2:
        cmpl    $10, -4(%rbp)
        jle     .L3
        leave
        ret
        .cfi_endproc


Выводит:
0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
7: 0
8: 0
9: 0
10: 39
11: 3
12: 2
13: 2
14: 2
15: 3
16: 4
17: 3
18: 4
19: 9
20: 999929
21: 0
То есть чаще всего первая нить успевает выполниться до запуска второй.
А если сделать счет не до 10, а до 10000. Или скажем ввести рандомные задержки на несколько миллисекунд после каждого инкремента.
Добавил точную синхронизацию момента старта функции worker в потоках:

static int together;

void* f(void *p)
{
        __sync_add_and_fetch(&together,1);
        while(__sync_add_and_fetch(&together,0) < 2);
        worker();
}


Это превратилось в такой код:

f:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $8, %rsp
        movq    %rdi, -8(%rbp)
        lock addl       $1, together(%rip)
.L6:
        movl    $0, %edx
        movl    %edx, %eax
        lock xaddl      %eax, together(%rip)
        addl    %edx, %eax
        cmpl    $1, %eax
        jle     .L6
        movl    $0, %eax
        call    worker
        leave
        ret
        .cfi_endproc


и замедлило работу ~ в 100 (!!!) раз.

После укорачивания внешнего цикла в 100 раз результат такой:

2:      1
3:      1
4:      1
6:      4
7:      1
8:      9
9:      5
10:     7044
11:     99
12:     56
13:     98
14:     130
15:     350
16:     144
17:     291
18:     473
19:     304
20:     989


Т.е. ответ: «да, 2 достижима на практике» (:
Думаю, в итоге в русском языке закрепится термин «тред».
Условия задачи слишком неопределённы, чтобы дать обоснованный ответ. Надеюсь, интервьюером был не представитель отдела кадров, с которым на данную тему не поспоришь.
Почему же? Правильный ответ — «результат неопределен»
А кому нужен такой ответ? Программистов берут на работу решать конкретные задачи. То, как он решает оторванные от реальности задачи, ничего не скажет о нём как о практике.
Чтобы знать как делать правильно, надо знать как не делать неправильно
Выяснить как правильно и есть решение задачи. В данной задачи условий недостаточно, а следовательно нет критериев оценки правильности любого предложенного решения.

Спорить тут не о чем, если хочется оставить последнее слово за собой, пожалуйста:
> Выяснить как правильно и есть решение задачи.

Не верно. Задача была — выяснить у собеседуемого, что он знает как может себя вести некорректный код, представленный в задании.
Текст обсуждаемой задачи привёден в топике. Мотивы интервьюера не указаны. Моё высказывание относилось к решению задач вообще и являлось ответом на пространное замечание постом выше.

Мой исходный пост указывал на некорректность условий задачи из топика, как если бы на собеседовании предложили решить подобное уравнение:

Решите уравнение:

2? 3 = x
где? — какая-то операция

Чему будет равен x и почему?


Сразу остужу пыл — правильный ответ здесь один — «это не уравнение». Человек, взявшийся решать это «уравнение», очевидно считает себя телепатом и в таком случае ему лучше попробоваться в «Битве экстрасенсов» или в чём-то подобном.
> Мотивы интервьюера не указаны

В задаче стоял вопрос «почему», который, на мой взгляд, интерпретируется однозначно: интервьюер хотел предметных объяснений как будет работать приведенный код.

Так что с мотивами все в порядке.
Оценка поста негативная. Инвертируем утверждение из поста и получаем: «Программистов берут на работу кроссворды разгадывать.»

До кризиса может и брали, сейчас нет.
Sign up to leave a comment.

Articles