Потоки и память

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

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

Для начала изучите следущий код и попробуйте ответить на простой вопрос:
Что произойдет, если запустить эту программу?
Чтобы упростить понимание напишу небольшое описание: этот код в цикле пытается создать и запустить 100тыс потоков. Потоки просто выделяют 100байт памяти и засыпают.
Программа выдает assert, пишет на экран число созданых потоков и выходит, если вдруг CreateThread не создал поток.
Программа была скомпилирована, как Console application с помощью Visual Studio 2005 (допускаю, что с другими компиляторами результаты могут разниться).
Итак, повторю вопрос: что произойдет, если запустить эту программу?

DWORD __stdcall MyThread(LPVOID lpThreadParameter)
{
  char* p = new char[100];
  Sleep(10000000);
  return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
  for(int i = 1; i < 100000; ++i)
  {
    HANDLE hThread = CreateThread(0, 0, MyThread, 0, 0, 0);
    if (!hThread)
    {
      printf("Maximum threads: %d\n", i);
      assert(hThread);
      break;
    }
    CloseHandle(hThread);
  }
  return 0;
}



Так вот, для 32 битной WinXP правильный ответ - программа всегда выходит с ошибкой, создав всего 2028 потоков!
При этом при запуске debug сборки выдается очень странное сообщение об ошибке в виде Message box-a - про ошибочную релокацию системной dll user32.dll. Это происходит при вызове assert, а почему это происходит - вы поймете позже.
При этом Task Manager не дает никаких подсказок о том, что не так - программа занимает 20Мб памяти и Virtual Memory Size всего 30Мб. То есть, получается, что 1 поток отъедает всего 16Кб памяти…

Почему же создано всего 2028 потоков?
Подсказку дает Process Explorer - он умеет показывать правильный Virtual Size, а не обрезаный, который показывает Task Manager. И Process Explorer показывает, что Virtual Size больше 2Гб. А это уже значит, что программа израсходовала всю память и больше ничего сделать не может. Даже выделить совсем немного памяти, чтобы загрузить системные dll, чтобы показать assert.
Получается, что каждый поток на самом деле занимает около 1Мб в памяти.
И это верно для программ, скомпилированных в Visual Studio. По умолчанию линкер там задает размер стэка для потоков в 1Мб. Изменить это можно через ключ /STACK в линкере.
Соответственно, когда процесс создает новый поток, он сразу выделяет (резервирует) этому потоку 1Мб памяти, т.к. стэк должен уметь расти и не может быть реалокирован или перемещен.
Так что помните, что каждый поток по умолчанию отъест у вас 1Мб памяти, потому что поток должен иметь стэк, а стэк - это просто непрерывный кусок памяти, который нужно зарезервировать заранее.

А можно ли это изменить?
Можно, конечно.
Можно изменить значение по умолчанию в линкере, используя ключ /STACK, а можно использовать второй параметр (dwStackSize) при создании потока в CreateThread. Изменять дефолтный размер стэка в параметрах линкера не очень хорошо, так как этот параметр влияет на все потоки, создаваемые в процессе. Лучше задавать размер стэка (dwStackSize) при создании потока.
Итак, давайте поменяем CreateThread(0, 0, MyThread, 0, 0, 0) на CreateThread(0, 100000, MyThread, 0, 0, 0). Как вы думаете, сколько потоков будет создано теперь?
Ответ, как ни странно - опять 2028!
Но эта ошибка уже от невнимательности чтения MSDN и кривости WinAPI. Там в описании флагов написано, что если флаги не указаны (предпоследний параметр == 0), то второй параметр используется не для размера стэка, а для размера commit size стэка. Думаю, уже не одна тысяча программистов поймалась на эту ошибку в названии параметра dwStackSize, который при определенных условиях может быть совсем не StackSize, а StackCommitSize.
Так что правильно надо писать: CreateThread(0, 100000, MyThread, 0, STACK_SIZE_PARAM_IS_A_RESERVATION, 0).

Запускаем. Результат - 15690 потоков. Опять странный результат. Стэк получается не 100КБ, а около 128КБ. Давайте попробуем так: CreateThread(0, 128*1024, MyThread, 0, STACK_SIZE_PARAM_IS_A_RESERVATION, 0).
Запускаем. Результат абсолютно тот же - 15690. Как и написано в MSDN, система увеличивает размер до размера ближайшей страницы. Но размер страницы 4К. Откуда же берется 64К? Оказывается, стэк резервируется функцией VirtualAlloc, а у нее гранулярность - 64K, так что хотя в MSDN и написано “страница”, а на самом деле должно быть “гранулярность VirtualAlloc”.

Проверим минимальный стэк и удалим new из функции потока для чистоты эксперимента: CreateThread(0, 1024, MyThread, 0, STACK_SIZE_PARAM_IS_A_RESERVATION, 0).
Запускаем. Результат - 30157 потоков. Это и есть максимум потоков для моей системы, т.к. каждый из потоков резервирует 1 страницу в 64К в памяти, а ограничение памяти - 2Гб. Можно увеличить до 3-х, тогда получится создать около 45000 потоков.
На 64-битных системах этого ограничения памяти нет, но я на них не тестировал и не знаю, сколько потоков создастся.

Потом ещё как-нибудь напишу про то, как потоки влияют на скорость работы программы в разных ситуациях. А на сегодня хватит - и так много букв.
Надеюсь эта информация кому-нибудь пригодится.

Понравилась статья? Подпишись на RSS!

Похожие записи:
Знай свою память
Книги + программисты = деньги
Не будите спящего программиста
2 признака кода с душком: убей его и лови всё молча
Я умею программировать на С++!?

Рекомендую книги по этой теме:

56 комментариев к Потоки и память

  • Egor

    Очень интересная статья.
    Замечу только, что, почитав Рихтера, большинство программистов создает новые потоки не через CreateThread, а через _beginthreadex().
    Но ни чего собственно это не меняет, так как и в этом макросе есть параметр stack_size и initflag куда надо будет записать STACK_SIZE_PARAM_IS_A_RESERVATION.

  • Да забавно. Интересно для чего вообще может понадобиться такая уйма потоков? Скажем огромная стейт-машина или программа снимающая данный с тысяч датчиков, хотя и тут все можно оптимизировать и убирать лишний потоки. Огромное количество потоков трудно контролировать, особенно если синхронизация продумана не до самых глубин. К примеру, не так давно наткнулся на эту статью и думал что я знал все о синхронизации, ан нет.
    Кстати в openMP кажется размер стека варьируется автоматически, но я могу ошибаться.

    • Столько потоков не нужно и вредно. На синхронизацию уже будет тратиться больше времени, чем на полезную работу. Так что я сильно против большого числа потоков.

  • Такое количество потоков может понадобится после чтения статей Intel.
    А использовать, действительно, лучше _beginthread вместо CreateThread, т.к. иначе при закрытии потока получится утечка равная размеру структуры _tiddata.

  • Никогда не задумывался, а сколько можно создать процессов?

    • Хм, интересно было бы проверить.
      Написать простейшую минимальную программку, с минимальным стэком и в цикле запускать их.
      Моя ставка - больше 65536 штук не запустится :)

  • Reperio

    Про уйму я имел ввиду количество > 10000. Про Intel это намек на многоядерность? В данном случае openMP проявляет себя надежнее, нежели вручную ворочать все потоки и кидать их на доступные ядра.
    А еще лучше инкапсулировать все эти _beginthread или CreateThread.

  • Если говорить о Windows, то OpenMP явно не надежнее собственных потоков, т.к. не позволяет контролировать их падение в полной мере. Некоторые рантайм функции могут вызвать TerminateProcess из-за мелочей. Например, так делает localtime, если туда передать 0.

  • girtablilu

    В статье есть неточность. На стандартной системе (WinXP, Vista) x86, x64 размер страницы равен 4Кб.
    Откуда же берутся 64Кб? Дело в том, что для каждого потока выделяется память для стека с помощью ф-ии VirtualAlloc(), для каждого потока покрайней мере один вызов. VirtualAlloc выделяет(резервирует) память(адресное пространство) с гранулярностью 64Кб. Т.е. каждый новый регион будет выровнен по 64К.

    Для 64-разрядных систем, создание потоков закончится, тем что исчерпается не виртуальная память в 8Тб, а физическая память в системе, т.е. RAM + файл подкачки и потоки перестанут создаваться.

    Что касается количества процессов, то здесь зависимость есть как от потоков, т.к. каждый процесс имеет хотя бы один поток, так и от размера системного пула, где содержатся системные объекты. Об этом можно почитать в последнем посте блога Марка Руссиновича (http://blogs.technet.com/markrussinovich/archive/2009/03/26/3211216.aspx) Следующий пост будет так же на эту тему.

    • Да, про то, что используется именно VirtualAlloc я не знал, спасибо.
      А на 64-разрядных физическая память исчерпается нескоро, т.к. commit size на каждый поток не такой большой - 16К на моей системе, так что на 8Гб подкачки и 2Гб памяти можно будет создать почти миллион тредов.

  • Вова

    со /STACK бывают неприятные истории, когда в процесс загружается либа рассчитывающая совсем на другой размер стэка.

    Например в XP(не смотрел в Vista) в PE rundll32.exe размер стека установлен на 256кб. дергаем из своей либы функцию, которая выполнится в основном потоке rundll32 и удивляемся почему в наших процессах все ок, а в rundll32 всегда stack overflow.

    • Да, есть такая опасность. Но, имхо, 256К - это очень много для стэка. И, если стэк больше получается, то это надо исправлять.
      Плохо только то, что всплывают эти ошибки в самый неподходящий момент. Да и разобраться в ошибке, если стэк переполнился - непросто.

  • Rei

    От себя хотел добавить, что реально создается 30158, так как у нас есть основной поток, который запускает создание потоков. Более того, так как флаг в настройках вижуальника не стоит, под основной поток будет по дефолту выделено 1мб. Так же, путем нехитрых вычислений получается, что все потоки (если убрать выделение по 100 байт) отжирают 1 931 072кб, в то время как у нас доступно 2 097 152кб. Разница 166 080кб. Можно попробовать после ассерта начать выделять память через new, небольшими блоками, для того, что бы узнать, сколько у нас в запасе осталось. Остаток поделить на 30158 и узнать, сколько еще тратиться на каждый поток. Так как помимо стека обязательно должна быть структура-дескриптор потока. Продолжаю исследования, уж больно долго они создаются :)

  • Rei

    Вообще, даже не надо алоцировать память. Итак понятно - если мы не смогли создать еще один поток, значит осталось меньше 64кб, которыми в таких масштабах можно принебречь :)
    Что мы имеем
    1 931 072кб - все потоки, включая основной
    30 157 потоков выделяют по 100 байт в сумме примерно 2 945кб

    (2 097 152 - 1 931 072 - 2 945) / 30 158 = у меня получилось чуть больше 5кб. Это тот объем, который уходит на дискриптор (а точнее контекст) потока, т.е. некая структура для шедулера.

    • Угу, похоже на то. А если new убрать - что-нибудь изменится?

      • Rei

        Ну в теории, без new у нас должен появится еще запас под определенное кол-во потоков :) Сейчас проверю на WinCE, там ограничение в 32мб на процесс, быстрее получается достигать лимита :)

        • Просто есть подозрение, что new 100 на самом деле выделяет больше, чем 100 байт. Там тоже страницами выделение идет, но вот размещает ли он внутри выделенной страницы остальные new 100 и как именно размещает - это вопрос.

          • Rei

            Ммм… new в итоге выделяет через HeapAlloc. Изначально под кучу выделяется определенное количество байт через виртуал аллок. Далее, все кто выделяют через new - выделяют ее из этой кучи. Но куча, разуеется может расти, по мере необходимости может расти и подгружать дополнительные страницы через виртуал аллок.

            Мои исследования показали, правда на WinCE, но ядро очень похоже на NT, точнее оно и развивалось из ядра NT 3.51
            На WinCE максимум 32 мега на процесс. Дефолтный размер стека 64к. Мне удалось создать 481 тред + 1 основной тред. После этого я стал по одному кб выделять память. Екнулся на 185кб. Итого, что мы имеем - 482 потока по 64к = 30848 + 185кб из кучи. 32768 всего - 31033, получается остаток 1 735кб. Куда они делись? Если разделить на 482, получается по 3.6кб на брата, как-то так :)

  • Спасибо за тему! Буквально недавно потоки вспоминал, а мучался с IO. Предложение по теме, может рассмотрите способы отладки многопоточных приложений.

  • позновательно…
    с другой стороны, такое число поток редко встретишь на практике.

    например, путь, когда серверное приложение под каждый сокет создает поток, насколько я помню, далеко не самое эффективное решение.

    • Да, 2000 потоков ни разу не встречал. Но цель исследования изначально была другая - посчитать расход памяти на 1 поток.

      • Полезный research. Я наконец-то утряс в голове связь между потоками, стеком и стековыми регистрами процессора :)
        Я раньше и не задумывался, что стек для каждого потока свой :)

        • если такие дыры в знаниях, надо не блоги читать, а какую-нить книжецу фундаментальную…

          • У… если так рассуждать, у меня очень много дыр в знаниях :) Я знаю миллион вещей про которые я не знаю…
            А еще я знаю, что есть тысяча и одна фундаментальная книга, которые мне было бы полезно прочитать… Один момент только - я не знаю их названий ;)

        • Я знал, что он свой. Но не знал, кто, когда, сколько и откуда его выделяет. Собственно, это и выяснял во время исследования :)

    • Rei

      В этой чудесной операционной системе, виндавс (хотя она, конечно, хороша, я не спорю) функция select работает через WSAWaitForMultipleEvents, которая в свою очередь работает через WaitForMultipleObjects, у которой, в свою очередь :) ограничение на 64 объекта. Т.е. фактически один тред может работать одновременно только с 64 сокетами. Теперь представим, что у нас какой-нить игровой сервак, и нам надо держать на нем 50-60 тысяч коннектов…. Вот вам и ситуация, когда надо 1000+ тредов. И это только для того, что бы держать соединения.

      • Или можно запустить еще один процесс. На этом же или на другом сервере ;)

      • еще один “сюжет для небольшого романа”.
        про такое веселое ограничение WaitForMultipleObjects никогда не слышал…
        64 объекта — что за магическое число? типа ddword? для 32-х разрядных систем dword вроде роднее…

        • Rei

          Ну, все вопросы к разработчикам системы :)
          Открываем MSDN, читаем описание WaitForMultipleObjects
          nCount
          [in] Specifies the number of object handles in the array pointed to by lpHandles. The maximum number of object handles is MAXIMUM_WAIT_OBJECTS.

          Открываем winnt.h
          находим
          #define MAXIMUM_WAIT_OBJECTS 64 // Maximum number of wait objects

          • да, я уже нарыл…

            гугл на MAXIMUM_WAIT_OBJECTS просто стену плача выдает… :)

          • Интересно, откуда взялась цифра 64 - чем обусловлена?
            Кто-то когда-то решил, что 64 всем будет достаточно и так и сделал, а теперь мучайся. Также, как в свое время с 2Гб ограничения по оперативной памяти в Windows.

            • 2 Gb идет от 32-х разрядного адресного пространства — это физическое ограничение. И от того, что в свое время ровно его половину M$ себе под системные нужды отхапала. Есть же ключик /3Gb, но обычно +1 Gb user спейса не спасает отца русской демократии.

        • girtablilu

          Если такие дыры в знаниях, надо не блоги читать…

        • Думаю, они используют биты внутри WaitForMultipleObjects и число там 64-битное, отсюда и ограничение. Но бред тот еще. У меня одна отличная красивая схема коммуникации недавно накрылась из-за этой “фичи”, т.к. мне надо было хотя бы пару сотен эвентов одновременно ждать.
          Да, это все можно обойти, создав новые треды, но столько гемора непонятно зачем…

      • Да, ограничение в 64 объекта в WaitForMultipleObjects - это тот еще бред. Но всё-таки, если мне склероз не изменяет, в играх на один сервак больше 5-10 тыс. клиентов лучше и не коннектить. А 10тыс - это всего 200 тредов по 64 сокета.
        Остальных 10 тыс лучше перевести на второй сервак и т.п.

        • Rei

          Все проще, судя по вакансиям, которые предлагают игровики, они свои MMO серваки на линкусе делают. Да и в целом, даже если венда, скорее всего уж на сервере будет 64битная, там Wait может больше объектов ждать :)

          • Разве в 64 битной MAXIMUM_WAIT_OBJECTS больше?
            И насчет линукса - если там игровой мир должен крутиться, то надо кросплатформенный код сразу писать. Все ли его пишут?
            Думаю, лучшие - да, на линукс сразу ориентируются, а большинство и на Win32 будет работать, т.к. там одновременно 10000 игроков и не бывает :)

  • Запустил вот такое под UNIX:

    #include <pthread.h>
    #include <stdio.h>

    void* action(void* p) {
    sleep(10);
    return 0;
    }

    int main() {
    int i = 0;
    while (1) {
    i = i + 1;
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 8*1024);
    if (pthread_create(&thread, &attr, action, 0) != 0) {
    perror("pthread_create()");
    printf("%d\n", i);
    exit(1);
    }
    pthread_attr_destroy(&attr);
    }
    return 0;
    }

    Linux Redhat AS5 64-bit, 8 CPU, 8Gb RAM:

    pthread_create(): Resource temporarily unavailable
    3225

    Solaris 5.10 SPARC 64-bit, 4 CPU, 8Gb RAM:

    pthread_create(): Not enough space
    2197325

    Компилировал с ключами по умолчанию. Думаю, если поиграться с размером стека, то на линуксе тож можно значительно увеличить эту цифру.

    • А почему sleep(10)? Это 10 мс или 10 секунд? В любом случае мало.
      Очень странная цифра под Redhat получилась - всего 3225 потоков и уже все ресурсы заняты на 64 битной системе. Что именно там кончилось?

  • Стоп, опечатался я — не 3225, а 32255.

    Какая-то мутная тема с потоками в линуксе. Насколько я знаю, с точки зрения внутренней организации в системе линукс не делает различия между процессами и потоками — все это потоки ядра, просто некоторые логически объединены в процессы. Поэтому 32225 очень похоже на максимальное число процессов всей системы. В солярисе это не так. Там есть четкое понятие LWP - low weight process, то есть поток-поток, который гораздо дешевле и быстрее, чем поток-процесс.

    В целом, а пока не понял, почему получается именно 32255 в линуксе.

    А sleep() в unix оперирует секундами.

    • 32255 действительно похоже на жесткий лимит - то, что осталось от 32768, если это максимум. Можно проверить предположение, запустив 2 тестовых приложения. Если будет по 16 тыс. в каждом - это точно лимит на уровне системы

  • Вот ведь программистский мозг! Чуть что, так сразу “а что если две сразу запустить!? ;-)

    Пробуем!


    hml-rh5xeon-~/test: threads& threads&
    [1] 931
    [2] 932
    hml-rh5xeon-~/test:
    pthread_create(): Resource temporarily unavailable
    16129
    pthread_create(): Resource temporarily unavailable
    16127

    [2] + Done(1) threads& threads&
    [1] - Done(1) threads& threads&

    Предположение подтвердилось.

    • Ага, выяснили опытным путем, что в 64 битном линуксе ограничение на число потоков-процессов в 32768 :) Странное ограничение.
      Надеюсь в серверных версиях у них такого ограничения нет?

  • Вообще-то я запускаю именно на серверной версии.

    Странно, насколько я знаю “cat /proc/sys/kernel/threads-max” в линуксе должна сказать, сколько можно создать в системе потоков. Обычно это число выбирается исходя из объема оперативки, чтобы было более менее оптимально для производительности. На этой машине эта команда говорит:

    cat /proc/sys/kernel/threads-max
    122579

    что согласуется с параметрами машины.

    Все-таки мне думается, что ~32K потоков - это огранические на юзерском уровне.

    • 32К пер юзер? Странное ограничение, если уж всего 122К, то зачем ограничивать.
      Но в любом случае это не так важно - 30000 потоков - это само по себе уже зло и не надо столько создавать.
      Правда это сейчас верно, а через 5-10 лет возможно уже компы по 100-1000 процессоров будут иметь и тогда обработка 30000 потоков будет нормальным делом даже на обычных юзерских машинах.

  • SS

    //Но эта ошибка уже от невнимательности чтения MSDN и кривости WinAPI. Там в описании флагов
    //написано, что если флаги не указаны (предпоследний параметр == 0), то второй параметр
    //используется не для размера стэка, а для размера commit size стэка
    Скорее все таки от кривости документации и реализации… в MFC для CWinThread::CreateThread
    1-й параметр dwCreateFlags - Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:

    CREATE_SUSPENDED Start the thread with a suspend count of one. Use CREATE_SUSPENDED if you want to initialize any member data of the CWinThread object, such as m_bAutoDelete or any members of your derived class, before the thread starts running. Once your initialization is complete, use the CWinThread::ResumeThread to start the thread running. The thread will not execute until CWinThread::ResumeThread is called.

    0 Start the thread immediately after creation.

    2-й параметр nStackSize - Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size as that of the process’s primary thread.
    т.е. тоже самое что указываем 1 что 65536 создать больше 2000 потоков программа не может, и нет даже намека на какие то другие значеня 1-го параметра чтобы заставит установить размер стека!!!
    И только посмотрев исходный код CWinThread::CreateThread можно обнаружить что вызывается

    m_hThread = (HANDLE)(ULONG_PTR)_beginthreadex(lpSecurityAttrs, nStackSize,
    &_AfxThreadEntry, &startup, dwCreateFlags | CREATE_SUSPENDED, (UINT*)&m_nThreadID);
    для которой в MSDN-е тоже написано что параметр
    initflag - Initial state of a new thread (0 for running or CREATE_SUSPENDED for suspended) и все :(
    но мы видим что параметры передаются в вызываемую еще ниже ф-цию WinApi
    CreateThread()
    для которой
    dwCreationFlags - The flags that control the creation of the thread. If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state, and will not run until the ResumeThread function is called. If this value is zero, the thread runs immediately after creation.
    И наконец то что то мы искали
    If the STACK_SIZE_PARAM_IS_A_RESERVATION flag is specified, the dwStackSize parameter specifies the initial reserve size of the stack. Otherwise, dwStackSize specifies the commit size.

    А вот и причина почему этого флага нет в описаниях класса MFC
    Windows 2000/NT and Windows Me/98/95: The STACK_SIZE_PARAM_IS_A_RESERVATION flag is not supported.
    Просто изначально его не поддерживали, а поддержка появилась относительно недавно начиная с Windows XP.
    Т.е. чтобы установить резервируемый размер стека потока, необходимо вызвать метод CWinThread::CreateThread
    pTh->CreateThread( CREATE_SUSPENDED|STACK_SIZE_PARAM_IS_A_RESERVATION,65536 );
    вот и все :)
    Спасибо автору, за статью, за найденную “кривость” и за то что подкинул тему для исследования.

    • Спасибо автору, за статью, за найденную “кривость” и за то что подкинул тему для исследования.

      Незачто :) В MSDN куда ни плюнь - везде “кривость” и тема для исследования :)

  • Hamital

    Ситуация вот какая. Меня недавно попросили поработать модератором на форуме. И, чёрт возьми, мне это понравилось:)
    А вот недавно я увидел ваш форум и прикинул: А почему бы и здесь не попроситься в модеры. Вопрос: можно ли у вас стать модератором, и если да, то как?

  • Как иногда хочется отдохнуть, расслабиться после трудного дня. Посмотреть интересное кино, послушать любимую музыку, почитать любимый журнал или захватывающую книжку. Дело, как может показаться на первый взгляд, за малым – найти желаемое в гигантской глобальной сети, скачать и сколько душа пожелает наслаждаться скачанным. Почти на всяком веб-сайте есть возможность скачать, то, что надо, но, или за большую стоимость, или же за отправку сомнительных сообщений. И вот, уже почти прекратив это дело, я случайно зашел на web-сайт, где нашлось всё, что мне нужно и не только, причем абсолютно бесплатно! С помощью комфортного поиска я за маленький срок нашел новейшие зарубежные фильмы, о которых очень много слышал и давным-давно собирался сам «оценить». Тем, кому больше нравятся отечественные видеофильмы, можно скачать свежие сериалы и новинки российского кино. Так же выложены хорошие документальные фильмы, спортивные передачи. Понравились знаменитые и прославившиеся мультики. У отдельного человека персональные вкусы в музыке. Для одних музыка – это замечательный способ отдохнуть, кого-то она располагает на положительный лад, поднимает самочувствие. А тем, кто предпочитает быстрые танцы, можно отобрать Mp3 в стиле диско. А я верен только единственному музыканту, собираю абсолютно все альбомы знаменитости, трепетно предвкушая новинки. Без музыки и жизнь неинтересна, без музыки весь мир казался бы мрачным и угрюмым. Если захотелось развлечься, отпустить негативные эмоции? Есть свободное время? Отдохнуть от повседневной жизни, уличной суеты и людей смогут спасти прекрасные компьютерные игры. В таких играх, как 3D Action, аркады, квесты и т.п., возможно проделать то, что в обыденной жизни не получится, расслабиться, отвлечься от бытовых дел, снять стресс или же наоборот украсить жизнь хорошей порцией адреналина. Для детей подойдут ролевые, стратегии, симуляторы, они прекрасно развивают смелость, память, реакцию.

  • lineage la2 Epilogue Slineage.com

    ==================

    http://www.Slineage.com

    ==================

Ответить

 

 

 

Вы можете использовать эти HTML тэги

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>