Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Программирование под Unix/Linux > threads


Автор: Artemon 2.12.2007, 14:46
1. Есть класс 
2. Есть внешняя ф-ия по отношению классу, но объявленная как friend


Внутри класса создаю поток и в качестве параметра передаю ему ту самую внешнюю фию и сам экземпляр класса.

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

На первый взгляд кажется все чинно, но вот попробовла подряд создать около 300 таких класов с потоками и вдруг мне Linux начал говорить что ему нехватает памяти на создание потока ?

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



Автор: JackYF 2.12.2007, 14:55
Artemon, ну ты понял, код давай сюда smile

Автор: bsa 2.12.2007, 21:09
Artemon, у тебя потоки NPTL или старые линуксовые?

Автор: MAKCim 2.12.2007, 21:29
Artemon, 
давай сюда код
Цитата(bsa @  2.12.2007,  21:09 Найти цитируемый пост)
Artemon, у тебя потоки NPTL или старые линуксовые? 

в плане использования памяти разницы нет

Автор: bsa 3.12.2007, 13:10
MAKCim, разве? я читал, что NPTL отличается тем, что не выполняется копирование страниц памяти процесса - значит память выделяется... Видимо, что-то не то читал...

Автор: MAKCim 3.12.2007, 13:49
Цитата(bsa @  3.12.2007,  13:10 Найти цитируемый пост)
разве? я читал, что NPTL отличается тем, что не выполняется копирование страниц памяти процесса - значит память выделяется... Видимо, что-то не то читал...

любой процесс в Linux, созданный с помощью clone() с указанием флага CLONE_VM, получает в "наследство" адресное пространство родителя
и LT, и NPTL реализованы с помощью clone() + CLONE_VM
другое дело, что NPTL использует для создания потока еще и CLONE_THREAD, который специально ввели для эффективной имплементации POSIX-потоков
CLONE_THREAD включает в себя следующие нововведения:
1. Группы потоков (TGID, TID)
2. Изменение семантики exec*, exit, работы сигналов
(вообще там много всего, но это основное)

Автор: bsa 3.12.2007, 14:11
MAKCim, спасибо, буду знать.

Автор: xvr 3.12.2007, 15:31
Цитата(Artemon @ 2.12.2007,  14:46)
1. Есть класс 
2. Есть внешняя ф-ия по отношению классу, но объявленная как friend


Внутри класса создаю поток и в качестве параметра передаю ему ту самую внешнюю фию и сам экземпляр класса.

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

А сам поток при этом завершается? pthread_join вызывали или detached статус потоку ставили?

Цитата

На первый взгляд кажется все чинно, но вот попробовла подряд создать около 300 таких класов с потоками и вдруг мне Linux начал говорить что ему нехватает памяти на создание потока ?

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

Память - да, а вот собственно потоки - неизвестно smile

Автор: vinick 5.12.2007, 13:18
Цитата(Artemon @  2.12.2007,  14:46 Найти цитируемый пост)
но вот попробовла подряд создать около 300 таких класов с потоками

Каждый поток имеет свой собственный стек, по умолчанию стек равен 8 метров. Получается если в какой-то момент у процесса оказалось 300 рабочих потоков, то только их стек занял 2.5 гига виртуальной памяти. Всего на 32-битной системе процесс  может забрать около 3 гиг,  кажется даже чуть меньше. Вот и получается нехватка памяти.

Автор: MAKCim 5.12.2007, 13:50
vinick, 
в таком случае используется swap
+ стек расширяется динамически, по мере надобности

Автор: vinick 5.12.2007, 15:50
Цитата(MAKCim @  5.12.2007,  13:50 Найти цитируемый пост)
vinick, 
в таком случае используется swap
+ стек расширяется динамически, по мере надобности 

Своп используется когда заканчивается физическая оперативная память. Я же говорю о виртуальном адресном пространстве процесса, когда оно заканчивается то получается out of memory. В 32-битных системах процесс может адресовать всего 4 гига памяти, но в linux около гига забирает ядро и по-моему что-то отдается загруженным динамическим библиотекам, итого процессу на личные нужды остается меньше 3 гиг. На каждый созданный поток по умолчанию резервируется 8 метров стека из этого адресного пространства, если потоков много, то их стек съедает все.

Расширяемость стека к топику не относится. Но стало интересно 
Я не смог найти в сети ни подтверждений вашего тезиса ни опровержения. Но вот такой пример при ulimit -s == 8192 почучает ошибку сегментирования. Или стек надо какими-то системными вызовами принудительно расширять?
Код

int main()
{
  char buf[10*1024*1024];
  buf[0]=1;
  return 0;
}



Автор: bsa 5.12.2007, 16:06
Цитата(vinick @ 5.12.2007,  15:50)
В 32-битных системах процесс может адресовать всего 4 гига памяти, но в linux около гига забирает ядро и по-моему что-то отдается загруженным динамическим библиотекам, итого процессу на личные нужды остается меньше 3 гиг.

Ядру гиг не нужен. Просто память некоторых устройств (видеокарт, например) отображается в адресное пространство процессора. Это приводит к тому, что когда установлено 4Гига памяти, часть из нее перекрывается отображаемой памятью. И это не только в Linux.

Автор: vinick 5.12.2007, 16:18
bsa, 
цитата http://www.ibm.com/developerworks/ru/library/l-memmod/#N102ED
Цитата

Резервные страничные фреймы

Linux резервирует несколько станичных фреймов для кода ядра и структур данных. Эти страницы никогда не сбрасываются в файл подкачки на диск. Линейные адреса с 0x0 до 0xc0000000 (PAGE_OFFSET) используются кодом пользователя и кодом ядра. Адреса с PAGE_OFFSET до 0xffffffff используются кодом ядра.

Это означает, что из 4 GB только 3 GB доступны для пользовательского приложения. 


Не путайте физически установленную оперативную память и вирутальную память. Это немного разные вещи.

Автор: MAKCim 5.12.2007, 19:02
vinick, 
Цитата(vinick @  5.12.2007,  15:50 Найти цитируемый пост)
Расширяемость стека к топику не относится. Но стало интересно 

вот мое маленькое исследование
Код

#include <unistd.h>
#include <signal.h>
#include <string.h>

extern void __code(void);

static void __handler(int signal) {
}

int main() {
    struct sigaction action;
    memset(&action, 0, sizeof(action));
    action.sa_handler = &__handler;
    sigaction(SIGUSR1, &action, NULL);
    __code();
    return 0;
}

Код

.data
.quad 0
.text
.globl __code
__code:
    movq    %rsp, (.data)
    callq   pause
    xchgq   (.data), %rsp
    subq    $524288, %rsp
    movl    $1, (%rsp)
    xchgq   (.data), %rsp
    callq   pause
    ret

Код

# gcc test.S main.c -o main
# ./main

далее смотрим /proc/3862/maps (PID = 3862)
Код

# cat /proc/3862/maps

получаем
Цитата

...
7fff3e355000-7fff3e36c000 rwxp 7fff3e355000 00:00 0                      [stack]
...

7fff3e36c000 - 7fff3e355000 = 94208 байт
далее посылаем процессу сигнал SIGUSR1
Код

# kill -s SIGUSR1 3862

опять смотрим /proc/3862/maps
Цитата

...
7fff3e2e7000-7fff3e36c000 rwxp 7fff3e2e7000 00:00 0                      [stack]
...

7fff3e36c000 - 7fff3e2e7000 = 544768
отсюда можно сделать однозначный вывод о расширении стека вниз
и он будет расширяться, пока не будет достигнут лимит на его размер
этот лимит равен ~ 8MB

Автор: Artemon 7.12.2007, 13:25
Код моего приложения примерно следующий:

Код


void MyFunc(void* Data)
{

  C1* p = (C1*)Data;
  delte p;
}

calss C1
{
public:
         C1(){ create_thread(...MyFunc, this...) }
};


int main()
{
  C1* val = NULL;

  for( int k = 0; k < 300; k ++ )
      {
       val = new C1();
       Sleep(1000); 
      }

  return 0;
}

Автор: xvr 7.12.2007, 14:30
Цитата(Artemon @ 7.12.2007,  13:25)
Код моего приложения примерно следующий:

Код


void MyFunc(void* Data)
{

  C1* p = (C1*)Data;
  delte p;
}

calss C1
{
public:
         C1(){ create_thread(...MyFunc, this...) }
};


int main()
{
  C1* val = NULL;

  for( int k = 0; k < 300; k ++ )
      {
       val = new C1();
       Sleep(1000); 
      }

  return 0;
}

Про create_thread попдробнее. Что вызывается и с какими параметрами?

Автор: Artemon 7.12.2007, 14:51
Особых настроек здесь нет, только переменная для ID потока и собственно функция потока.

Автор: xvr 7.12.2007, 14:55
Цитата(Artemon @ 7.12.2007,  14:51)
Особых настроек здесь нет, только переменная для ID потока и собственно функция потока.

Код

$ man create_thread 
No manual entry for create_thread

Как ДОСЛОВНО называлась (и вызывалась) функция запуска thread'а ?

Автор: Artemon 7.12.2007, 16:31
result = pthread_create(&tid, NULL, NetThread, (void*)this);

Автор: bsa 7.12.2007, 17:09
Artemon, а где pthread_join?

Автор: vinick 7.12.2007, 17:16
Artemon,  Потоки надо либо ловить с помощью pthread_join, либо отсоединять. Иначе все ресурсы занятые потоком не освобождаются после его завершения.

В вашем коде проще всего отсоединять потоки.
1. 
Либо сразу после создания
Код

result = pthread_create(&tid, NULL, NetThread, (void*)this);  
pthread_detach(tid);


2. 
Либо используя аттрибут PTHREAD_CREATE_DETACHED
Код

void MyFunc(void* Data)
{
  C1* p = (C1*)Data;
  delte p;
}
calss C1
{
public:
         C1(const pthread_attr_t * attr){pthread_t tid; pthread_create(&tid,attr,MyFunc, this); }
};
int main()
{
  C1* val = NULL;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACH);
  for( int k = 0; k < 300; k ++ )
      {
       val = new C1(&attr);
       Sleep(1000); 
      }
  return 0;
}

Автор: Artemon 19.12.2007, 10:01
Всем спасибо, задача разрешилась.
Особое спасибо vinickУ

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)