Russian Qt Forum
Май 17, 2024, 03:13 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 2 [3] 4   Вниз
  Печать  
Автор Тема: Нитки и очередь  (Прочитано 26324 раз)
BRE
Гость
« Ответ #30 : Октябрь 21, 2009, 22:19 »

Набросал немного кода:
Код
C++ (Qt)
#include <boost/noncopyable.hpp>      
#include <boost/thread/thread.hpp>    
#include <boost/thread/mutex.hpp>    
#include <boost/bind.hpp>            
#include <boost/shared_ptr.hpp>      
#include <iostream>                  
#include <queue>                      
 
boost::mutex            mutexCout;              // Блокировка std::cout
 
boost::mutex            mutexDatas;
boost::condition        condDatas;
 
std::queue<int>         datas;
 
void thread_func()
{                
       for(;;)  
       {        
               boost::mutex::scoped_lock locker( mutexDatas );
               while( datas.empty() )                        
                       condDatas.wait( locker );              
 
               int val = datas.front();
               if( val == -1 )        
                       break;          
 
               datas.pop();
               locker.unlock();
 
               {
                       boost::mutex::scoped_lock locker( mutexCout );
                       std::cout << "Process thread " << val << std::endl;
               }                                                          
               sleep( 3 );                                                
               {                                                          
                       boost::mutex::scoped_lock locker( mutexCout );    
                       std::cout << "Stop thread " << val << std::endl;
               }
       }
}
 
// Помещение данных в очередь
// -1 специальное сообщение обозначающее, что данных больше не будет
void push_data( int v )
{
       boost::mutex::scoped_lock locker( mutexDatas );
       datas.push( v );
       condDatas.notify_one();
}
 
int main( int, char ** )
{
       boost::thread th1( &thread_func );
       boost::thread th2( &thread_func );
       boost::thread th3( &thread_func );
 
       for( int i = 0; i < 20; ++i )
               push_data( i * 10 );
 
       sleep( 5 );
 
       for( int i = 0; i < 20; ++i )
               push_data( i * 10 );
 
       push_data( -1 );
 
       th1.join();
       th2.join();
       th3.join();
 
       return 0;
}
 
Записан
spectre71
Гость
« Ответ #31 : Октябрь 22, 2009, 12:41 »

Добрый вечер
...
...

Наверное так:

proc_thread.h
Код
C++ (Qt)
//---------------------------------------------------------------------------
 
#ifndef proc_threadH
#define proc_threadH
 
//---------------------------------------------------------------------------
 
#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
 
/*-----------------------------------------------------------------------------*/
 
class TThreadData;
class TCommonThreadData;
class TProcThread;
 
/*-----------------------------------------------------------------------------*/
 
class TThreadData {
//
//
//
};
 
class TCommonThreadData : public QObject {
   Q_OBJECT
 protected:
   int RunThreads;
   int ThreadsCount;
   QAtomicInt Stopped;
   QMutex Mutex;
   QWaitCondition Wait;
   QList<TThreadData*> NewData;
 public:
   TCommonThreadData(int ThreadsCount, QObject* parent=NULL);
   virtual ~TCommonThreadData();
 public:
   inline bool stopped(void) const {return Stopped;}
   void wait   (void);
   TThreadData* nextThreadData(void);
   void completeThreadData(TThreadData* ReadyData);
 protected:
   void stopAll(void);
   void addThreadData(TThreadData* ThreadData);
 protected slots:
   void doCompleteThreadData(TThreadData* ReadyData);
 signals:
   void signalCompleteThreadData(TThreadData*);
};  
 
 
class TProcThread : public QThread {
   Q_OBJECT
 protected:
   TCommonThreadData* CommonData;
 public:
   TProcThread(TCommonThreadData* CommonData, QObject* parent=NULL);
   virtual ~TProcThread();
 protected:
   virtual void run (void);
 protected:
   inline void wait   (void)       {CommonData->wait();}
   inline bool stopped(void) const {return CommonData->stopped();}
 protected:
   void calculate(TThreadData* ThreadData);
};
 
/*-----------------------------------------------------------------------------*/
 
#endif
 

proc_thread.cpp
Код
C++ (Qt)
#include "proc_thread.h"
#include <QMutexLocker>
 
 
/***************************
*     class TCommonThreadData
***************************/

 
 
TCommonThreadData::TCommonThreadData(int ThreadsCount, QObject* parent) : QObject(parent) {
 this->ThreadsCount = ThreadsCount;
 RunThreads = 0;
 Stopped    = 0;
 connect(this , SIGNAL(signalCompleteThreadData(TThreadData*)), this , SLOT(doCompleteThreadData(TThreadData*)), Qt::QueuedConnection);
 for(int i=0; i<ThreadsCount; i++) {new TProcThread(this);}
}
 
TCommonThreadData::~TCommonThreadData() {
}
 
TThreadData* TCommonThreadData::nextThreadData(void) {
 QMutexLocker MutexLocker(&Mutex);
 if(NewData.isEmpty()) {return NULL;}
 RunThreads++;
 return NewData.takeFirst();
}
 
void TCommonThreadData::completeThreadData(TThreadData* ReadyData) {
 emit signalCompleteThreadData(ReadyData);
}                          
 
void TCommonThreadData::doCompleteThreadData(TThreadData* ReadyData) {
 //
 //  Work with ReadyData
 //                    
 //  delete ReadyData;
 //                    
 
 QMutexLocker MutexLocker(&Mutex);
 RunThreads--;
 if(!Stopped) {
   int Count = (NewData.count() <= ThreadsCount-RunThreads)?NewData.count():ThreadsCount-RunThreads;
   for(int i=0; i<Count; i++) {Wait.wakeOne();}
   return;
 }
 if(!RunThreads) {
   //
   // All Threads Completed
   //
 }
}
 
void TCommonThreadData::addThreadData(TThreadData* ThreadData) {
 QMutexLocker MutexLocker(&Mutex);
 NewData.append(ThreadData);                
 if(Stopped) {return;}
 Wait.wakeOne();
}
 
void TCommonThreadData::wait (void) {
 Mutex.lock();
 Wait.wait(&Mutex);
 Mutex.unlock();
}
 
void TCommonThreadData::stopAll(void) {
 Stopped.ref();
 Mutex.lock();
 Wait.wakeAll();
 Mutex.unlock();
}
 
 
 
/***************************
*     class TProcThread
***************************/

 
TProcThread::TProcThread(TCommonThreadData* CommonData, QObject* parent) : QThread(parent) {
 this->CommonData = CommonData;
 connect(this , SIGNAL(finished()), this , SLOT(deleteLater()));
 start();
}
 
TProcThread::~TProcThread() {
}
 
void TProcThread::run (void) {
 TThreadData* ThreadData;
 for(;;) {
   if(stopped()) {return;}
   wait();
   if(stopped()) {return;}
   ThreadData = CommonData->nextThreadData();
   if(!ThreadData) {continue;}
   calculate(ThreadData);
   CommonData->completeThreadData(ThreadData);
 }
}            
 
void TProcThread::calculate(TThreadData* ThreadData) {
 //
 //  Calculate ThreadData
 //                  
}
 
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #32 : Октябрь 22, 2009, 13:43 »

А вот что получилось у меня (очевидyые/несущественные детали опущены).
Для OSX (и, насколько мне известно Linux)

CSemaphore::Wait  - sem_wait
CSemaphore::Signal  - sem_post

Код:
void CThreadControl::PutJob( void * data )
{
if (!data) return;
CSemaphore::Wait(mClientJobSemaphore);
mJobList.PutJob((CRTJob *) data);
CSemaphore::Signal(mClientJobSemaphore);
}

bool CThreadControl::GetJob( void )
{
CSemaphore::Wait(mClientJobSemaphore);
CRTJob * job = mJobList.GetJob();

// do nothing (but return true) if no jobs
if (!job) return true;

switch (job->mID) {
// main thread is waiting for all jobs ready
case MPJobEnd:
// if all done then open semaphore for main thread
if (++mNumReady >= mThread.Count())
CSemaphore::Signal(mHostEndSemaphore);

// unlock queue semaphore
CSemaphore::Signal(mClientJobSemaphore);

// wait on mClientEndSemaphore
CSemaphore::Wait(mClientEndSemaphore);
if (mNumReady) {
if (--mNumReady > 0)
CSemaphore::Signal(mClientEndSemaphore);
else
CSemaphore::Signal(mHostBegSemaphore);
}
break;

// terminate all jobs
case MPJobQuit:
// unlock queue semaphore and return false to terminate the thread
CSemaphore::Signal(mClientJobSemaphore);
return false;

// it is a job to calculate
default:
// unlock queue semaphore and perform the job
CSemaphore::Signal(mClientJobSemaphore);
job->Eval();
break;
}
return true;
}

void CThreadControl::WaitJobs( void )
{
int i, limit = mThread.Count();
for (i = 0; i < limit; ++i)
PutJob(mJobList.Alloc(MPJobEnd));

CSemaphore::Wait(mHostEndSemaphore);
CSemaphore::Signal(mClientEndSemaphore);
}

void * theThreadFunc( void * )
{
while (theThreadControl.GetJob()) {}
return 0;
}
Пошел отлаживаться - мне там еще много глобальных переменных убрать надо. Всем спасибо за помощь/участие 
Записан
spectre71
Гость
« Ответ #33 : Октябрь 22, 2009, 14:15 »

Вот подправил немного:

TCommonThreadData::addThreadData - добавляем данные для расчета (ThreadData)
TCommonThreadData::noMoreNewData - вызываем когда данных(ThreadData) больше не будет
TCommonThreadData::onCalculatedData - вызовется когда расчитаны конкретные данные (ThreadData)
TCommonThreadData::onCompletedAllData - вызовется когда все данные расчитаны
TProcThread::calculate - расчет конкретных данных (ThreadData)

proc_thread.h
Код
C++ (Qt)
//---------------------------------------------------------------------------
 
#ifndef proc_threadH
#define proc_threadH
 
//---------------------------------------------------------------------------
 
#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
 
/*-----------------------------------------------------------------------------*/
 
class TThreadData;
class TCommonThreadData;
class TProcThread;
 
/*-----------------------------------------------------------------------------*/
 
class TThreadData {
//
//
//
};
 
class TCommonThreadData : public QObject {
   Q_OBJECT
 protected:
   int RunThreads;
   int ThreadsCount;
   bool NoMoreNewData;
   QAtomicInt Stopped;
   QMutex Mutex;
   QWaitCondition Wait;
   QList<TThreadData*> NewData;
 public:
   TCommonThreadData(int ThreadsCount, QObject* parent=NULL);
   virtual ~TCommonThreadData();
 public:
   inline bool stopped(void) const {return Stopped;}
   void wait   (void);
   TThreadData* nextThreadData(void);
   void completeThreadData(TThreadData* ReadyData);
 protected:
   void noMoreNewData(void);
   void addThreadData(TThreadData* ThreadData);
   void onCalculatedData(TThreadData* ReadyData);
   void onCompletedAllData(void);
 protected slots:
   void doCompleteThreadData(TThreadData* ReadyData);
 signals:
   void signalCompleteThreadData(TThreadData*);
};  
 
 
class TProcThread : public QThread {
   Q_OBJECT
 protected:
   TCommonThreadData* CommonData;
 public:
   TProcThread(TCommonThreadData* CommonData, QObject* parent=NULL);
   virtual ~TProcThread();
 protected:
   virtual void run (void);
 protected:
   inline void wait   (void)       {CommonData->wait();}
   inline bool stopped(void) const {return CommonData->stopped();}
 protected:
   void calculate(TThreadData* ThreadData);
};
 
/*-----------------------------------------------------------------------------*/
 
#endif

proc_thread.cpp
Код
C++ (Qt)
#include "proc_thread.h"
#include <QMutexLocker>
 
/***************************
*     class TCommonThreadData
***************************/

 
TCommonThreadData::TCommonThreadData(int ThreadsCount, QObject* parent) : QObject(parent) {
 this->ThreadsCount = ThreadsCount;
 RunThreads = 0;
 Stopped    = 0;
 NoMoreNewData  = false;
 connect(this , SIGNAL(signalCompleteThreadData(TThreadData*)), this , SLOT(doCompleteThreadData(TThreadData*)), Qt::QueuedConnection);
 for(int i=0; i<ThreadsCount; i++) {new TProcThread(this);}
}
 
TCommonThreadData::~TCommonThreadData() {
}
 
TThreadData* TCommonThreadData::nextThreadData(void) {
 QMutexLocker MutexLocker(&Mutex);
 if(NewData.isEmpty()) {return NULL;}
 RunThreads++;
 return NewData.takeFirst();
}
 
void TCommonThreadData::completeThreadData(TThreadData* ReadyData) {
 emit signalCompleteThreadData(ReadyData);
}                          
 
void TCommonThreadData::doCompleteThreadData(TThreadData* ReadyData) {
 onCalculatedData(ReadyData);
 QMutexLocker MutexLocker(&Mutex);
 RunThreads--;
 if(!NewData.isEmpty()) {
   Wait.wakeOne();
 } else if (NoMoreNewData) {
   if(!Stopped) {
     Stopped.ref();
     Wait.wakeAll();
   }
   if(!RunThreads) {onCompletedAllData();}
 }
}
 
void TCommonThreadData::addThreadData(TThreadData* ThreadData) {
 if(NoMoreNewData) {
   // throw ERROR usage addThreadData
 }
 QMutexLocker MutexLocker(&Mutex);
 NewData.append(ThreadData);                
 Wait.wakeOne();
}
 
void TCommonThreadData::wait (void) {
 Mutex.lock();
 Wait.wait(&Mutex);
 Mutex.unlock();
}
 
void TCommonThreadData::noMoreNewData(void) {
 if(NoMoreNewData) {return;}
 NoMoreNewData = true;
 QMutexLocker MutexLocker(&Mutex);
 if(NewData.isEmpty()) {
   Stopped.ref();
   Wait.wakeAll();
   if(!RunThreads) {onCompletedAllData();}
 }
}
 
void TCommonThreadData::onCalculatedData(TThreadData* ReadyData) {
 //
 //  Work with ReadyData
 //                    
 //  delete ReadyData;
 //                    
}
 
void TCommonThreadData::onCompletedAllData(void) {
 //
 // All Data Calculated
 //
}
 
/***************************
*     class TProcThread
***************************/

 
TProcThread::TProcThread(TCommonThreadData* CommonData, QObject* parent) : QThread(parent) {
 this->CommonData = CommonData;
 connect(this , SIGNAL(finished()), this , SLOT(deleteLater()));
 start();
}
 
TProcThread::~TProcThread() {
}
 
void TProcThread::run (void) {
 TThreadData* ThreadData;
 for(;;) {
   if(stopped()) {return;}
   wait();
   if(stopped()) {return;}
   ThreadData = CommonData->nextThreadData();
   if(!ThreadData) {
     // throw ERROR Synchronization
   }
   calculate(ThreadData);
   CommonData->completeThreadData(ThreadData);
 }
}            
 
void TProcThread::calculate(TThreadData* ThreadData) {
 //
 //  Calculate ThreadData
 //                  
}
« Последнее редактирование: Октябрь 22, 2009, 14:20 от Spectre » Записан
BRE
Гость
« Ответ #34 : Октябрь 22, 2009, 15:56 »

Обвернул свой вариант в класс:  Улыбающийся
Код
C++ (Qt)
#include <boost/noncopyable.hpp>                        
#include <boost/thread/thread.hpp>                      
#include <boost/thread/mutex.hpp>                      
#include <boost/thread/condition.hpp>                  
#include <boost/bind.hpp>                              
#include <boost/shared_ptr.hpp>                        
#include <iostream>                                    
#include <queue>                                        
#include <list>                                        
 
struct JobData
{            
       JobData( int i ) : x( i ), y( i ) {}
       int     x;                          
       int     y;                          
};                                          
 
class JobManager
{              
public:        
       bool begin( int numThread = 10 );
       bool end();                      
 
       void addJob( const JobData &data );
 
private:
       void threadFunc();
 
private:
       boost::mutex                 m_mutex;
       boost::condition              m_cond;
       std::queue<JobData*>    m_datas;        
 
       typedef boost::shared_ptr<boost::thread>        ThreadPtr;
       std::list<ThreadPtr>        m_threadPool;                    
};                                                                
 
bool JobManager::begin( int numThread )
{                                      
       if( m_threadPool.size() )      
       {                              
               std::cout << "Begin already running." << std::endl;
               return false;                                      
       }                                                          
 
       for( int i = 0; i < numThread; ++i )
       {                                  
               ThreadPtr thread( new boost::thread( boost::bind( &JobManager::threadFunc, this ) ) );
               m_threadPool.push_back( thread );                                                    
       }                                                                                            
 
       return true;
}                  
 
bool JobManager::end()
{                    
       if( !m_threadPool.size() )
       {                        
               std::cout << "End already running." << std::endl;
               return false;                                    
       }                                                        
 
       {
               boost::mutex::scoped_lock locker( m_mutex );
               m_datas.push( 0 );                          
               m_cond.notify_all();                        
       }                                                  
 
       for_each( m_threadPool.begin(), m_threadPool.end(), boost::bind( &boost::thread::join, _1 ) );
m_threadPool.clear();
 
       m_datas.pop();
 
       return true;
}                  
 
void JobManager::addJob( const JobData &data )
{                                            
       boost::mutex::scoped_lock locker( m_mutex );
       m_datas.push( new JobData( data ) );        
       m_cond.notify_one();                        
}                                                  
 
void JobManager::threadFunc()
{                            
       for(;;)              
       {                    
               boost::mutex::scoped_lock locker( m_mutex );
               while( m_datas.empty() )                    
                       m_cond.wait( locker );              
 
               JobData *val = m_datas.front();
               if( !val )                    
                       break;                
 
               JobData data( *val );
 
               m_datas.pop();
delete val;
               locker.unlock();
 
// Выполняем расчет используя data
// ...
       }
}
 

Использование:
Код
C++ (Qt)
{
       JobManager mng;
 
// Запускаем 5 ниток
       mng.begin( 5 );
for( ... )
mng.addJob( JobData(...) );
mng.stop()
 
// Запускаем 20 ниток
mng.begin( 20 )
for( ... )
mng.addJob( JobData(...) );
mng.stop()
 
       return 0;
}
 
« Последнее редактирование: Октябрь 23, 2009, 14:16 от BRE » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #35 : Октябрь 22, 2009, 16:36 »

А сравнить у кого быстрее? Улыбающийся  Ну понятно, не сию минуту а найти время на выходные.  Если есть интерес, что возьмем для расчетов?
Записан
BRE
Гость
« Ответ #36 : Октябрь 22, 2009, 19:21 »

А сравнить у кого быстрее? Улыбающийся  Ну понятно, не сию минуту а найти время на выходные.  Если есть интерес, что возьмем для расчетов?
Так все есть, подставь расчет и проверь.  Улыбающийся
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #37 : Октябрь 22, 2009, 20:23 »

Так все есть, подставь расчет и проверь.  Улыбающийся
Никому не хочется возиться с чужим кодом, искать нужные хедеры, либы и.т.п. А вот сделать тестовый пример со своей реализацией - несложно и интересно. Если есть идеи такого примера (что считать) - я поддержу
Записан
BRE
Гость
« Ответ #38 : Октябрь 23, 2009, 08:27 »

Никому не хочется возиться с чужим кодом, искать нужные хедеры, либы и.т.п. А вот сделать тестовый пример со своей реализацией - несложно и интересно. Если есть идеи такого примера (что считать) - я поддержу
Все равно тесты нужно гонять на одной машине.
Для варианта Spectre нужен Qt, для моего нужен boost.
Можно скопировать код с форума, если нужно могу выложить в отдельном архиве.
Записан
umts
Гость
« Ответ #39 : Октябрь 24, 2009, 19:24 »

Это весьма сложная задача (распределение нагрузки по процессорам, ядрам и т.д. и т.п.) для решения "руками". Тут можно посоветовать изучить OMP либо (идеальный вариант) Intel Threading Building Blocks, возможности TBB просто великолепны.
Ничего здесь сложно нет. ОС сама распределяет потоки по разным процессорам.


Как раньше написали, тебе надо всего лишь реализовать определенный шаблон проектирования.

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

Насчет главного потока. Я так понимаю, что результатов будет ровно столько же, сколько результатов. Здесь можно ждать совпадения кол-ва, но проще будет ожидание завершения всех потоков, т.к. если потоки завершились, то значит задания все решены, можно пожинать результаты. Здесь уже можно извлекать данные из очереди результатов и считать все, что хочешь.
Если процессоров достаточно много, то это действие можно выполнять также в отдельном потоке. Но это уже довольно сильное осложнение алгоритма.


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



Точно, а Intel TBB - это так, для лохов... Вы сначала почитайте про эту технологию, тогда поймете, в чем разница между тем, что делает ОС, и тем, что делает ПРАВИЛЬНАЯ технология многопоточного вычисления. Во-первых, автоматически определяется оптимальное число нитей, во-вторых, нет необходимости заботиться о запуске/останове потоков, в-третьих, существует понятие "воровство задач" - когда одна задача (нить) закончила выполнение, а другая еще нет - часть незаконченной задачи передается на выполнение отработавшей нити, в-четвертых, производится балансировка нагрузки. Перечислять можно еще долго, рекомендую ознакомиться, хотя бы для общего развития. ОС же просто запускает (если такая возможность существует в лоб) поток на другом ядре/процессоре и не более того.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #40 : Октябрь 25, 2009, 19:30 »

Отрихтовал и убрал пару багов, потестировал на машине с 2 процессорами по 2 ядра (итого 4).  В качестве рабочей функции для thread использовал генерацию случайного числа и вычисление его косинуса 1024 раз (Test1) и 4096 раз (Test2). Результаты интересные

          no threads         1 thread       4 threads        8 threads
----------------------------------------------------------------------
Test 1   1:57 (100%)   2:35 (132%)   1:05 (55%)   1:27(74%)
Test 2   7:40 (100%)   8:15 (107%)   2:13 (29%)   2:56(38%)

В таблице время (в мин:сек и в процентах) для разного числа threads. Как видно, при небольших задачах/вычислениях расходы на переключение/синхронизацию становятся значительными. Видимо для таких случаев надо объединять задачи в блоки/кучки
Записан
SABROG
Гость
« Ответ #41 : Октябрь 26, 2009, 19:53 »

А это какой вариант Qt или boost?
Записан
BRE
Гость
« Ответ #42 : Октябрь 26, 2009, 20:13 »

А это какой вариант Qt или boost?
Проверял на boost 1.34.1 и 1.37, думаю пойдет на boost с версии 1.25.0 (в которой появился модуль thread).

Sorry, не правильно прочел вопрос.  Смеющийся
« Последнее редактирование: Октябрь 26, 2009, 20:18 от BRE » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #43 : Октябрь 26, 2009, 20:16 »

А это какой вариант Qt или boost?
Нет, я проверял свою реализацию в которой нет ни Qt, ни boost  Улыбающийся
Записан
niXman
Гость
« Ответ #44 : Ноябрь 24, 2009, 04:10 »

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

Ну и мой личный интерес есть.
Записан
Страниц: 1 2 [3] 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.422 секунд. Запросов: 22.