Obiekty i thread safe

0

Cześć, mam pewien dylemat i chciałem się upewnić, czy dobrze myślę. Załóżmy, że mam metodę w klasie:

 
function TMyClass.Sum(a, b: integer): integer;
begin
  cs.Enter;
  result:=a+b;
  cs.Leave;
end;

cs to oczywiście TCriticalSection.

Mam też utworzony jeden obiekt tej klasy. Jest on używany w kilku wątkach. I teraz pytanie, czy taki zapis klasy jak wyżej wystarczy, żeby uczynić ją thread safe, czy powinienem raczej stosować sekcje na obiekt, tzn:

 
cs.Enter;
a:=ObjOfMyClass.Sum(1, 2);
cs.Leave;
0

To co pokazałeś jest thread safe nawet bez TCriticalSection:

function TMyClass.Sum(a, b: integer): integer;
begin
  result:=a+b;
end;

i już.

0

A możesz wyjaśnić dlaczego to jest thread safe?

0
Juhas napisał(a):

A możesz wyjaśnić dlaczego to jest thread safe?

Bo operujesz tylko na argumentach funkcji. Nie czytasz ani nie zmieniasz obiektu.

0

Aha, to teraz bardziej szczegółowo. Jest taka klasa(wersja bardzo uproszczona):

type
  TMyClass = class
  private
    FADO: TADOQuery;
  public
    function ExecuteQuery(sql: string): boolean;
end;

//i ciało
constructor Create();
  FADO:=TADOQuery.Create(nil);
end;

function ExecuteQuery(sql: string): boolean;
begin
  FADO.SQL.Text:=sql;
  try
    FADO.Execute;
    result:=true;
  except
    result:=false;
  end;
end;

Jak sprawa ma się teraz?

0

Zrób tak:

function ExecuteQuery(sql: string): boolean;
begin
          EnterCriticalSection(CritSect);
          try
            // przetwarzanie
          finally
            LeaveCriticalSection(CritSect);
          end;
end;

Przykład bez try/finally:
http://www.delphicorner.f9.co.uk/articles/op4.htm
http://edn.embarcadero.com/article/22411

Przykład dla nowszego Delphi:
http://docwiki.embarcadero.com/RADStudio/XE3/en/Using_Critical_Sections

0

Znowu nie potrzebujesz CS:

type
  TMyClass = class
  private
  public
    function ExecuteQuery(sql:String):Boolean;
end;
 
function TMyClass.ExecuteQuery(sql:String):Boolean;
var FADO:TADOQuery;
begin
  Result:=false;
  FADO:=TADOQuery.Create(nil);
  try
    FADO.SQL.Text:=sql;
    try
      FADO.Execute;
      result:=true;
    except
    end;
  finally
    FADO.Free;
  end;
end;

I niech ADO się martwi o synchronizacje.

0

Chodzi o to, żeby nie tworzyć ADO w executeQuery, tylko w konstruktorze. Mam np. takie pętle, w których wykonuję kilka nawet tysięcy insertów i muszę znać rezultat każdego z nich. I teraz tworzyć i niszczyć kilka tysięcy razy obiekt podczas jednej pętli, to... ;)

0

I myślisz że za pomocą synchronizacji przyspieszysz? - grubo się mylisz, wręcz odwrotnie.

0
Juhas napisał(a):

Chodzi o to, żeby nie tworzyć ADO w executeQuery, tylko w konstruktorze. Mam np. takie pętle, w których wykonuję kilka nawet tysięcy insertów i muszę znać rezultat każdego z nich. I teraz tworzyć i niszczyć kilka tysięcy razy obiekt podczas jednej pętli, to... ;)

To zupełnie nie tędy droga.
Jaką masz bazę danych?

  1. Poszukaj czegoś o bulk load dla swojej bazy
  2. Zrób CSV z danymi które chcesz zaimportować
  3. Odpal narzędzie masowego ładowania i załaduj CSV-a do tabeli importowej - pola np. VARCHAR(255)
  4. W SQL-u popraw dane zaimportowane lub przekonwertuj je do tabel docelowych

Odpalanie po jednym SQL-u na rekord - w każdym środowisku - będzie wolne.

Inne rozwiązanie - jeśli to MySQL: masz np. składnie pozwalającą wpisać np. 500 rekordów jedną komendą SQL:

INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(4,5,6),(7,8,9);

http://dev.mysql.com/doc/refman/5.5/en/insert.html

Ale to raczej tylko w MySQL-u. W każdej bazie jest inaczej.

0

_13th Dragon, wydaje mi się, że będzie bardziej stabilne. Poza tym sekcje krytyczne chyba nie są aż tak powolne?
vpiotr - ale ja używam tej klasy w kilku wątkach. Stąd sekcje. Moja baza to MSSQL. Raz mam jeden insert, raz 1000. Ale poczytam o tym bulk load.

0
Juhas napisał(a):

vpiotr - ale ja używam tej klasy w kilku wątkach. Stąd sekcje. Moja baza to MSSQL. Raz mam jeden insert, raz 1000. Ale poczytam o tym bulk load.

Jeśli nie potrzebujesz wyniku wykonania to pomyśl nad skorzystaniem ze zsynchronizowanej kolejki (struktury danych).

  • wiele wątków piszących do zsynchronizowanej kolejki -
  • jeden wątek czytający z kolejki, który wykonuje wszystkie zadania z kolejki

W ten sposób zadania piszące szybko będą mogły kontynuować pracę, a wątek zapisujący do bazy będzie równo pracował.

Kolejka może przechowywać zapytania w postaci rekordów: kod SQL (string), lista parametrów i ich wartości (Variant).

0
  1. Tam już jest synchronizacja (wewnątrz ADO) , dodając kolejny poziom synchronizacji - bardzo spowalniasz.
  2. Przy wywołaniu metody: function ExecuteQuery(sql:String):Boolean; (w tym wierszu: FADO.SQL.Text:=sql;) i tak tworzysz kopie napisu sql - więc przydzielasz znacznie więcej pamięci niż potrzebuje egzemplarz TADOQuery.
0

btw dla nowych wersji delphi jest FireDac.
Poczytaj o firedac i DMLArray.

http://docs.embarcadero.com/products/rad_studio/firedac/frames.html?frmname=topic&frmfile=uADStanOption_TADResourceOptions_ArrayDMLSize.html

A tak swoja droga taka liczba insertow to nie przesada jakas? Czy to naprawde ma uzasadnienie?

0

Ludzie, po co jakieś CS, synchronizacja, skoro i tak każdy wątek tworzy osobny obiekt i połączenie z db do której wali query, o ile się nie mylę to tak masz zrobione, bo utworzenie głównego obiektu połączenia z db i podpięcie do niego wątków, które tylko wywołują zapytanie (przez obiekt bazodanowy) jest głupotą.
Jedyne co można by zrobić to try except by logować nieudane/błędne zapytania. Pamiętaj, żaden uproszczony schemat kodu nie odda prawdziwego stanu tak jak to zrobi oryginał.

0

OMG - Wszystkie wątki korzystają z tego samego, jednego obiektu globalnego. Pisałem o tym w pierwszym poście.
Crowa - tak, te inserty mają uzasadnienie.

0

Wybacz, zasugerowałem się drugim postem kolegi @_13th_Dragon
To powiem ci że popełniasz błąd waląc query w kilku wątkach na jednym kontrolerze, tak się nie powinno robić, bezpieczniej tworzyć kolejne połączenia na wątki.

0

dokladnie osobne polaczenia na watek lub praca asynchroniczna (mozna to ustawic w connection string)

0

Kwestia jest taka, że nie tworzę wszystkich wątków sam. Tzn. upraszczając. Główna aplikacja wywołuje funkcję DBProc, w której jest kilka zapytań SQL. To jest wykonywane pod przyciskiem. Ale aplikacja pracuje z SDKami, które mają swoje callbacki. I teraz, jeśli SDK odpala jakiegoś swojego callbacka, to wtedy jest wywoływana też funkcja DBProc. Czyli wg Was najlepiej byłoby wyposażyć aplikację powiedzmy w 2 obiekty TADOConnection, dodać parametr do DBProc typu TADOConnection i po prostu odpalać DBProc z odpowiednim połączeniem?

0

a connection pooling ? moze cos takiego Ci pomoze

http://dn.embarcadero.com/article/30027

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