Synchronizacja wątków - na zmianę, nie losowo

0

Witam. Ćwicząc pracę z synchronizacją między wątkami chciałem zrobić tak, aby 2 wątki pracowały na zmianę, dotychczas z moich obserwacji wynika, że raz jeden wątek wykona pracę 10x, a drugi 5x, potem znów pierwszy wykona ileś tam, a potem drugi. Chciałbym, aby odbywało się to po kolei i na zmianę czyli: wątek1, wątek2, wątek1, wątek2. Jak to wykonać na poniższym kodzie? Dodam, że kiedyś czytałem o czymś takim, tylko nie pamiętam gdzie i znaleźć nie mogę.

PS Wiem, że to można wykonać za pomocą zmiennych pomocniczych, ale chyba da się zrobić to bez nich, właśnie o ten sposób pytam.

        static Thread th1;
        static Thread th2;
        static StreamWriter writer;
        static Semaphore sema;

        static void Main(string[] args)
        {
            th1 = new Thread(new ThreadStart(th1_Task));
            th2 = new Thread(new ThreadStart(th2_Task));
            writer = new StreamWriter("test.txt");
            sema = new Semaphore(0, 1);
            sema.Release();    
            th1.Start();
            th2.Start();
        }

        static void th1_Task()
        {
            while (true)
            {
                sema.WaitOne();
                writer.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
                sema.Release();
            }
        }

        static void th2_Task()
        {
            while (true)
            {
                sema.WaitOne();
                writer.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId);
                sema.Release();
            }
        }
0

Twój kod działa nie tak, jakbyś chciał, bo po uwolnieniu semafora, zanim nastąpi przełączenie na drugi wątek, pętla kolejny raz się obraca i znowu jest zabierany semafor przez ten sam wątek. Co rozumiesz przez zmienne pomocnicze? Gdzieś musi być przechowana informacja, który wątek ostatni chwytał semafor, bez tego nie sądzę, żeby dało się to zrobić.

0

Hmm dobra, czyli jak zrobić tak jakbym chciał żeby było prawidłowo?

0

A po co ci w ogóle wątki w takim przypadku?

0

Nie znam C#, anyway w tym wypadku wystarczy użyć dwóch semaforów. Jeden wątek trzyma - drugi próbuje uwolnić i tak naprzemiennie.

0

@Rev chciałem tylko pokazać o co mi chodzi, potrzebuję to do połączenia z bazą danych, jest współdzielone między kilkoma wątkami i nie można dopuścić do jednoczesnego dostępu do tego połączenia, więc jak "zablokować" to połączenie fachowo?

1

Już w kodzie, który podałeś, połączenie jest poprawnie zablokowane. Co prawda chcesz, żeby wątki odpalały się jeden po drugim, ale, jak wyżej, po co w takim wypadku w ogóle używać wątków. Rozumiem, że dla czystego treningu, możesz to zrobić np. tak:

 
        static Thread th1;
        static Thread th2;
        static Semaphore sema;
        static int nextThread = 1;

        static void Main(string[] args)
        {
            th1 = new Thread(new ThreadStart(th1_Task));
            th2 = new Thread(new ThreadStart(th2_Task));
            sema = new Semaphore(0, 1);
            sema.Release();
            th1.Start();
            th2.Start();
            Console.ReadLine();
            th1.Abort();
            th2.Abort();
        }

        static void th1_Task()
        {
            while (true)
            {
                while (nextThread == 2) ;
                sema.WaitOne();
                Console.WriteLine("Thread 1");
                Thread.Sleep(100);
                nextThread = 2;
                sema.Release();

            }
        }

        static void th2_Task()
        {
            while (true)
            {
                while (nextThread == 1) ;
                sema.WaitOne();
                Console.WriteLine("Thread 2");
                Thread.Sleep(1000);
                nextThread = 1;
                sema.Release();
            }
        }
0

Czyli co, inaczej, bez zmiennej nie da rady? Nie da się tu właśnie wykorzystać semafora (nie do blokowania, a właśnie kolejności)?

0

takie coś mam do zabawy. działa na moim kompie naprzemiennie, ale tylko dla sleepów nie mniejszych od 10ms (nie muszą być równe). im czas krótszy od 10ms tym więcej błędów.

using System;
using System.Threading;

public class Program
{
	private void p1()
	{
		while (true)
		{
			lock (this)
			{
				Console.WriteLine("1");
				Thread.Sleep(50);
			}
		}
	}
	
	private void p2()
	{
		while (true)
		{
			lock (this)
			{
				Console.WriteLine("2");
				Thread.Sleep(10);
			}

		}
	}
	

	Program()
	{
		Thread t1 = new Thread(new ThreadStart(p1)), t2 = new Thread(new ThreadStart(p2));

		t1.Start();
		t2.Start();
		t1.Join();
	}
}
0

bez sensu tworzyć dwa wątki skoro mają działać na zmianę i zawsze tylko jeden jednocześnie
to można po prostu sprowadzić do jednego wątku i pętli

while(true)
{
    funkcja_watku_pierwszego();
    funkcja_watku_drugiego();
}

ja bym zrobił z tego wątku taki wątek rozporządzający, dał do tego delegat i zwyczajnie możliwość dopisywania nowych funkcji przez +=

watek.funkcje_do_wywolania += funkcja_watku_pierwszego;
watek.funkcje_do_wywolania += funkcja_watku_drugiego;

itd. a w wątku tylko:

while(true) funkcje_do_wywolania(/* tutaj ewentualnie rzeczy do przekazania funkcji, np to połączenie */);

a jeżeli nie zależy ci na przemienności (co jest jak mówiłem bez sensu) to po prostu skorzystaj z locka na połączeniu

lock(polaczenie)
{
}

jeżeli dasz lock na tym samym obiekcie w dwóch wątkach to jeden będzie czekał aż ten drugi go zwolni

aha, nigdy nie stosuj

lock (this)

tak jak w kodzie ŁFa powyżej
przypuśćmy że będziesz miał klasę agregującą która będzie miała w sobie te wątki
teraz np jeżeli obiekt robi locka sam na sobie a jednocześnie klasa agregująca będzie chciała to zrobić na nim a wewnątrz wykonać metodę tej klasy to dojdzie do dead-locka czyli locka z którego program już nie wyjdzie

jeżeli nie masz już na czym zalockować obiektu to stwórz pusty, prywatny obiekt:

Object obiekt_do_lockowania = new object();

i jego wykorzystaj do lokowania
jako że nie będzie on dostępny z zewnątrz to nie dojdzie nigdy do sytuacji przypadkowego zakleszczenia programu

ale bawiąc się w naprzemienne wątki, tak jak pisał Rev, musiałbyś użyć dwóch semaforów
albo lepiej, jako że używałbyś tych semaforów tylko w stanie 0 lub 1, użyć AutoResetEvent:

            AutoResetEvent are1 = new AutoResetEvent(true); // jeden z nich musisz na początku wpuścić
            AutoResetEvent are2 = new AutoResetEvent(false);

// watek 1:
                        are1.WaitOne();
                        Debug.WriteLine("watek 1");
                        Thread.Sleep(rand.Next(20));
                        are2.Set();

// watek 2:
                        are2.WaitOne();
                        Debug.WriteLine("watek 2");
                        Thread.Sleep(rand.Next(20));
                        are1.Set();

jedyna różnica w kodzie między wykorzystaniem semafora a autoresetevent to bardziej intuicyjna nazwa metody "Set" zamiast "Release"

1
xeo545x39 napisał(a):

potrzebuję to do połączenia z bazą danych, jest współdzielone między kilkoma wątkami i nie można dopuścić do jednoczesnego dostępu do tego połączenia, więc jak "zablokować" to połączenie fachowo?
Czyli chodzi ci o zabronienie jednoczesnego dostępu czyli to co miałeś w swoim pierwszym poście. Kolejność nie ma tu znaczenia przecież. Dostęp będzie się rozkładać sprawiedliwie. Im częściej jakiś wątek będzie żądać dostępu tym częściej go dostanie. Może i będą małe wahnięcia, ale suma summarum rozłoży się to proporcjonalnie.

A jeśli wątek może pracować tylko i wyłącznie posiadając dostęp do zasobu (do bazy), a nie mając tego dostępu nie ma nic innego do roboty to w takim razie błędem jest tu użycie wątków, są one zbędne, bo i tak nie mogą działać współbieżnie.

// EDIT

Może inaczej powinieneś to zrobić. Może kolejność ma tu jakieś znaczenie jednak? Niestety nie opisałeś problemu bardziej szczegółowo.

Może (może) powinieneś zrobić jeden wątek do rozmowy z bazą danych i kilka innych wątków do przeprowadzania obliczeń. I teraz w zależności od specyfiki twojego problemu odpowiednio komunikować się między wątkami i odpowiednio je synchronizować.

Przykład. Chcesz przeprowadzić wymagające obliczenia dzięki którym sklecisz odpowiednie zapytanie do bazy. Tak więc startujesz wątki obliczeniowe, niech sobie liczą. Startujesz też wątek do rozmowy z bazą. Używając np. System.Threading.Barrier blokujesz wątek rozmowy dopóki wątki obliczeniowe nie zakończą obliczeń.

Inny przykład. Wątki obliczeniowe sobie liczą i wyznaczają dane. Ich kolejność nie jest istotna. Wyniki swoich obliczeń wrzucają do kolejki (synchronizowanej), wątek rozmowy z bazą pobiera z kolejki gotowe dane i wysyła do bazy.

Widzisz. Z synchronizacją nie ma lekko. To specyfika danego problemu określa jak powinna być przeprowadzona synchronizacja. Nie wiele jest przepisów ogólnych. Ty nie podałeś problemu, a tylko wyobrażenie jego rozwiązania. Nie ma dobrej odpowiedzi na postawione pytanie, gdyż taka sytuacją jaką opisałeś nie zdarza się w rzeczywistości.

1 użytkowników online, w tym zalogowanych: 0, gości: 1