Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> notify, wait, произвольный выбор потоков, ошибка документации? 
:(
    Опции темы
ROKR
  Дата 26.9.2009, 16:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



Имеется задание:
"Выполнить приложение командной строки с двумя параметрами , один из которых – количество потоков, другой параметр – количество выводимых строк
Нужно так синхронизовать потоки, чтобы имена потоков выводились поочередно- в каскадном порядке.
Под синхронизацией подразумевается использование конструкции synchronized и методов wait, notify.
Использование флагов для выполнения синхронизации не допускается.
(имя1 имя 2 имя 3 имя 4 имя 5 ….
имя 1 имя 2 имя 3 имя 4 имя 5 ….
.......)"

Привожу код, который написал:

Код

class B
{
    int count = 0;
    int max = 0;
    int count_a = 0;
    int strings = 0;
    B(int m,int s)
    {
        max = m;
        strings = s;
    }
    synchronized void print(String str,int c)
    {
        try
        {
            System.out.print(str + " ");
            count++;
            if(count == max)
            {
                count_a = count;
                System.out.println("-");
                count = 0;
            }
            if(count_a != 0)
            {
                notify();
                count_a--;
            }
            if(c == strings - 1)
            {
                return;
            }
            wait();
        }
        catch(InterruptedException e)
        {
            System.out.println(e);
        }
    }
}

class A implements Runnable
{
    private Thread thrd;
    private int strings;
    private B b;
    A(String name,int strings,B b)
    {
        thrd = new Thread(this,name);
        this.strings = strings;
        this.b = b;
    }
    public void run()
    {
            int i = 0;
            while(i != strings)
            {
                b.print(thrd.getName(),i);
                i++;
            }
    }
    void start()
    {
        thrd.start();
    }
    void join() throws InterruptedException
    {
        thrd.join();
    }
}
public class lab7
{
    static void usage()
    {
        System.out.println("Использование:");
        System.out.println("[количество потоков] [количество строк]");
        System.exit(0);
    }
    public static void main(String[] args)
    {
        B b = new B(Integer.valueOf(args[0]),Integer.valueOf(args[1]));
        A thrd[];
        try
        {
            if( args.length < 2 || Integer.valueOf(args[0]) <= 0
                     || Integer.valueOf(args[1]) <= 0)
            {
                usage();
            }
            int tmp = Integer.valueOf(args[0]);
            thrd = new A[tmp];
            for(int i = 0; i != tmp - 1; i++ )
            {
                thrd[i] = new A(Integer.toString(i),Integer.valueOf(args[1]),b);
                thrd[i].start();
                Thread.sleep(10);
            }
            thrd[tmp - 1] = new A(Integer.toString(tmp - 1),Integer.valueOf(args[1]),b);
            thrd[tmp - 1].start();
            Thread.sleep(10);
        }
        catch(NumberFormatException e)
        {
            usage();
        }
        catch(InterruptedException e)
        {
            System.out.println(e);
        }
    }

}



Кол-во потоков и кол-во строк передается в args[]. Создается экземпляр класса B, который потом передается в качестве параметра в класс A, реализующий потоки. В классе B метод print синхронизируется. Потоки запускаются через 10 мсек.

Опишу метод paint():

Код

synchronized void print(String str,int c)
    {
        try
        {
            System.out.print(str + " ");
            count++;
            if(count == max)
            {
                count_a = count;
                System.out.println("-");
                count = 0;
            }
            if(count_a != 0)
            {
                notify();
                count_a--;
            }
            if(c == strings - 1)
            {
                return;
            }
            wait();
        }
        catch(InterruptedException e)
        {
            System.out.println(e);
        }
    }


Когда поток приходит в метод, выводится его номер, затем поток вызывает wait(). count нужно для перевода строки. max - кол-во потоков. с и strings текущая строка потока и требуемое для вывода кол-во строк соответственно.

Вот тут начинается расхождение. Моя идея лежит в следующем: Когда приходит последний поток, он, перед wait`ом вызывает notify(), таким образом пробуждая первый поток, вызвавший wait(). Затем, пока count_a != 0, вызывается notify(), пробуждая потоки в том порядке, в котором они пришли в метод paint() и вызывали wait(). Следуя документации, такого быть не должно:
Цитата

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.

notify()
Однако, вывод оказывается следующим:
Цитата

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23...
...


Тестировал код при разных кол-вах потоков (от 100 до 2000) и строк(10 1000). Тестировал на разных компьютерах. Результат один. Преподаватель говорит, что код не правильный, и что если 1000 раз выводилось верно, то на 1001 результат будет не верный.

Почему тогда при выводе нет разброса потоков?

-----------------------------------------------------------------

Скачал Шилдта - Java 2 The Complete Reference. Там написано следующее:
Цитата

notify( ) wakes up the first thread that called wait( ) on the same object.

стр. 297 
PM MAIL   Вверх
revenforv
Дата 26.9.2009, 17:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



А собственно какой вывод требуется на выходе, контрольный пример, пожалуйста.  smile 
PM MAIL Skype   Вверх
ROKR
Дата 26.9.2009, 17:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



В самом начале пример:

Цитата(ROKR @  26.9.2009,  16:58 Найти цитируемый пост)
(имя1 имя 2 имя 3 имя 4 имя 5 ….
имя 1 имя 2 имя 3 имя 4 имя 5 ….
.......)


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

имя 1 имя 5 имя 3 имя 4 имя 2 ….

Этого же мнения придерживается и преподаватель.

Это сообщение отредактировал(а) ROKR - 26.9.2009, 17:19
PM MAIL   Вверх
revenforv
Дата 26.9.2009, 17:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Вообще-то нет.. в документации написано
Цитата

The choice is arbitrary and occurs at the discretion of the implementation.


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

В общем как-то так (давно я этим не занимался - могу и соврать).
PM MAIL Skype   Вверх
revenforv
Дата 26.9.2009, 17:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Посмотрю как можно решить задачу - через полчасика напишу, что получилось.
PM MAIL Skype   Вверх
ROKR
Дата 26.9.2009, 18:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



Вот, и я думаю также, но!, преподаватель уперлась в слово "arbitrary". И хоть бы что =). Или найти в книге Шилдта упоминание про очередь.
PM MAIL   Вверх
revenforv
Дата 26.9.2009, 19:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



В общем задача прочитать то что я написал и понять идею:
Код

public class LabWork7 {
    
    private static final String HELP_MSG = 
        "Usage:\tjava LabWork7 <n> <m>\n" +
        "\t<n>\tintegral amount of active threads\n" +
        "\t<m>\tintegral amount of repetitions";
    
    private static final String EXIT_MSG =
        "Program finished normally";
    
    public static void main(String[] args) {
        if(args.length < 2) {
            System.out.println(HELP_MSG);
            return;
        }
        
        int threadsNum = 0, repsNum = 0;
        try {
            // reading input
            threadsNum = Integer.parseInt(args[0]);
            repsNum = Integer.parseInt(args[1]);
        } catch(NumberFormatException e) {
            System.out.println(HELP_MSG);
            return;
        }
        
        // producing output
        Printer p = new Printer(threadsNum, repsNum);
        Thread[] ths = new Thread[threadsNum];
        for(int i=0; i<threadsNum; i++) {
            ths[i] = new MyThread(Integer.toString(i), p, repsNum);
            ths[i].start();
        }
        
        // exit(0)
    }
    
}

// Prints strings to line separated by ' '.
// The size of the line is finite and then reached - new line begins.
class Printer {
        
    private int lineSize;
    private int lineAmount;
   
    private int linePos;
    private int queuePos;
    
    // n - amount of elements in line;
    // am - amount of lines
    public Printer(int n, int am) {
        if(n < 0)
            throw new IllegalArgumentException("Argument value must be non-negative");
        
        this.lineSize = n;
        this.lineAmount = am;
    }
    
    // method is synchronised over this object
    public synchronized void print(String elem) {
        System.out.print(elem + " ");
        linePos++;
        
        if(linePos == lineSize) {
            System.out.println("-");
            lineAmount--;
            linePos = 0;
            queuePos = lineSize;
        }
        
        if(queuePos > 0) {
            notify();
            queuePos--;
        }
        
        if(lineAmount == 0) {
            notifyAll();
            return;
        }
        
        try {
            wait();
        } catch(InterruptedException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

class MyThread extends Thread {
    
    private Printer aPrinter;
    private int repsNum;
    
    // r - amount of repetitions
    public MyThread(String name, Printer p, int r) {
        super(name);
        
        this.aPrinter = p;
        this.repsNum = r;
    }
    
    @Override
    public void run() {
        while(repsNum > 0) {
            aPrinter.print(this.getName());
            repsNum--;
        }
    }
}


Добавлено @ 19:37
Сравнить с той же программой, но в которой метод print() заменен на:
Код

// method is synchronised over this object
public synchronized void print(String elem) {
    System.out.print(elem + " ");
    linePos++;
        
    if(linePos == lineSize) {
        System.out.println("-");
        lineAmount--;
        linePos = 0;
        queuePos = lineSize;
    }
    
    if(queuePos > 0) {
        queuePos--;
    }
        
    if(lineAmount == 0) {
        return;
    }
}


Это сообщение отредактировал(а) revenforv - 26.9.2009, 19:56
PM MAIL Skype   Вверх
revenforv
Дата 26.9.2009, 20:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Тест 1 (для первоначального варианта программы - с очередью синхронизации)
Код

>java LabWork7 10 20
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
0 2 1 4 3 6 7 5 9 8 -
>Exit code: 0


Тест 2
Код

>java LabWork7 10 20
0 0 0 1 0 0 0 0 0 0 -
0 0 0 0 0 1 1 1 1 1 -
1 0 0 0 0 0 0 4 4 4 -
4 4 4 4 4 4 4 4 4 4 -
4 4 4 1 2 2 2 2 2 2 -
1 1 4 4 4 4 3 3 3 3 -
8 8 8 9 9 9 9 9 9 9 -
9 9 9 9 9 9 9 9 9 9 -
9 9 9 5 5 5 5 5 5 5 -
5 5 5 5 5 5 5 5 5 5 -
5 5 5 1 2 2 1 1 1 1 -
1 1 1 1 1 6 2 2 2 8 -
8 8 8 8 8 8 8 8 8 8 -
8 8 8 8 8 8 3 3 3 3 -
3 3 3 3 3 3 3 3 3 3 -
3 3 7 7 7 7 7 7 7 7 -
7 7 7 7 7 7 7 7 7 7 -
7 7 2 2 2 2 2 2 2 2 -
2 6 6 6 6 6 6 6 6 6 -
6 6 6 6 6 6 6 6 6 6 -
>Exit code: 0

PM MAIL Skype   Вверх
ROKR
Дата 26.9.2009, 20:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



Вот что нашел в книге Core Java2 Volume 2 Advenced Features:
Цитата

What happens if there is more than one runnable thread with the same (highest) priority? One
of the highest priority threads gets picked. It is completely up to the thread scheduler how to
arbitrate between threads of the same priority. The Java programming language gives no
guarantee that all of the threads get treated fairly. Of course, it would be desirable if all threads
of the same priority are served in turn, to guarantee that each of them has a chance to make
progress. But it is at least theoretically possible that on some platforms a thread scheduler
picks a random thread or keeps picking the first available thread. This is a weakness of the
Java programming language, and it is difficult to write multithreaded programs that are
guaranteed to work identically on all virtual machines.

А в Шилдте вот что:
Цитата

In cases where two threads with the same priority are competing for CPU cycles, the
situation is a bit complicated. For operating systems such as Windows 98, threads of
equal priority are time-sliced automatically in round-robin fashion. For other types of
operating systems, threads of equal priority must voluntarily yield control to their peers.
If they don’t, the other threads will not run.

Т.е., как я понял все работа с потоками ложится на ОС, а не на JVM.
PM MAIL   Вверх
revenforv
Дата 26.9.2009, 20:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



А теперь пару слов о неявных очередях и синхронизации потоков:
1. Приоритет потоков не влияет на порядок выборки потоков из locking set (в Sun JVM).
2. Текущая реализация Sun JVM поддерживает неявную реализацию locking set в виде очереди синхронизации. Однако, эту особенность нельзя учитывать при написании реальных программ, т.к. в будущем все может измениться.
Цитата

The notify method will wake up one thread waiting to reacquire the monitor for the object. You cannot be certain which thread gets woken. If you have only one waiting thread then you do not have a problem. If you have multiple waiting threads then it will probably the thread that has been waiting the longest that will wake up. However you cannot be certain, and the priorities of the threads will influence the result.

3. В описанной выше программе после прохода первой строки всегда остается активным только один поток, который и освобождает только один поток. И это первый поток, который ждет в очереди.
Цитата

"..when a running thread calls wait the thread enter waiting state where it waits in a que associated with the particular object on which wait was called . The first thread in the wait queue for a particular object becomes ready on a call to notify issued by another thread associated with the object. If more than one thread is in queue then it is better to call notifyAll method."

"Java How To Program" by Deitel & Deitel (p. 695)


Добавлено @ 20:27
Цитата(ROKR @  26.9.2009,  21:05 Найти цитируемый пост)
Т.е., как я понял все работа с потоками ложится на ОС, а не на JVM. 


Не совсем так.. я для того и привел два примера метода print(). Первый полагается на синхронизационное множество, за которым следит JVM (и определяет его реализацию). А второй - синхронизуется средствами ОС.
Т.е. в действительности только ОС обеспечивает arbitrary выборку потоков (которые, к слову блокированы ею же - к синхронизации Java это не имеет отношения, JVM только гарантирует, что два потока не будут одновременно выполнять synchronised метод).


Это сообщение отредактировал(а) revenforv - 26.9.2009, 20:28
PM MAIL Skype   Вверх
ROKR
Дата 27.9.2009, 16:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



Цитата(revenforv @ 26.9.2009,  20:22)
1. Приоритет потоков не влияет на порядок выборки потоков из locking set (в Sun JVM).
2. Текущая реализация Sun JVM поддерживает неявную реализацию locking set в виде очереди синхронизации. Однако, эту особенность нельзя учитывать при написании реальных программ, т.к. в будущем все может измениться.

Где про этот факт можно узнать?
PM MAIL   Вверх
revenforv
Дата 27.9.2009, 18:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Продемонстрируйте на примере вот этой программки, которая для N потоков сначала печатает их номера с префиксом F и блокирует их, а потом (после разблокирования) печатает с префиксом S.
Все потоки поочередно с перерывом в 500 мск разблокируются еще одним потоком.
Обратить внимание на то, что:
1. Порядок печати номеров совпадает
2. Сначала печатаются все F, а потом все S
Код

public class SimpleLocks {
    
    private static final int SIZE = 5;

    public static void main(String[] args) {
        for(int i=0; i<SIZE; i++) {
            final int id = i;
            new Thread() {
                @Override
                public void run() {
                    print_with_lock(id);
                }
            }.start();
        }
        
        new Thread() {
            @Override
            public void run() {
                for(int i=0; i<SIZE; i++) {
                    unlock();
                    try {
                        sleep(500);
                    } catch(Exception e) {}
                }
            }
        }.start();
    }
    
    ////////////////////////////////////////////////
    
    public static synchronized void print_with_lock(int i) {
        try {
            System.out.printf("F%03d ", i);
            SimpleLocks.class.wait();
            System.out.printf("S%03d ", i);
        } catch(Exception e) {}
    }
    
    public static synchronized void unlock() {
        SimpleLocks.class.notify();         
    }
    
    public static synchronized void unlockAll() {
        SimpleLocks.class.notifyAll();         
    }
}

Для сравнения запустить программу 2, в которой N+1 поток использует метод unlockAll() потоки будут напечатаны в произвольном порядке (разблокирование начинается через N*500 мск, чтобы печати с F не перемешивались с выводами S).
Код

public class SimpleLocks {
    
    private static final int SIZE = 25;

    public static void main(String[] args) {
        for(int i=0; i<SIZE; i++) {
            final int id = i;
            new Thread() {
                @Override
                public void run() {
                    print_with_lock(id);
                }
            }.start();
        }
        
        new Thread() {
            @Override
            public void run() {
                for(int i=0; i<SIZE; i++) {
                    try {
                        sleep(SIZE*100);
                    } catch(Exception e) {}
                    unlockAll();
                }
            }
        }.start();
    }
    
    ////////////////////////////////////////////////
    
    public static synchronized void print_with_lock(int i) {
        try {
            System.out.printf("F%03d ", i);
            SimpleLocks.class.wait();
            System.out.printf("S%03d ", i);
        } catch(Exception e) {}
    }
    
    public static synchronized void unlock() {
        SimpleLocks.class.notify();         
    }
    
    public static synchronized void unlockAll() {
        SimpleLocks.class.notifyAll();         
    }
}


PS. Кстати, можно заметить, что потоки по notifyAll вываливаются в обратном порядке т.е. реально Sun JVM использует в качестве арбитрарного алгоритма FIFO для notify() и LIFO для notifyAll(). Но, опять же, это недокументированная особенность реализации и про нее вам никто не скажет - она доказывается чисто эмпирически.
В качестве доказательства можете использовать Duck Test.

Это сообщение отредактировал(а) revenforv - 27.9.2009, 18:22
PM MAIL Skype   Вверх
ROKR
Дата 28.9.2009, 00:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 26.9.2009

Репутация: нет
Всего: 1



Спасибо за помощь! smile Но все же не понятно, почему пишут в документации одно, а на практике другое.
Поговорю еще с преподователем, но на всякий случай сделал еще двумя способами задачу: с помощью управляющего потока, и когда каждый поток пробуждает следующий.

Это сообщение отредактировал(а) ROKR - 28.9.2009, 00:10
PM MAIL   Вверх
revenforv
Дата 28.9.2009, 21:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 47
Регистрация: 6.8.2009
Где: Санкт-Петербург

Репутация: нет
Всего: нет



Цитата(ROKR @  28.9.2009,  01:09 Найти цитируемый пост)
 Но все же не понятно, почему пишут в документации одно, а на практике другое.

Вообще-то в документации написано четко:
Цитата

The choice is arbitrary and occurs at the discretion of the implementation.

Это и означает, что выбор произвольный и происходит по усмотрению реализации. Последовательный выбор - это тоже произвольный на усмотрение реализации. Так что в документации нет никакого противоречия, там ведь не сказано ничего типа randomly.
PM MAIL Skype   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1035 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.