الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

نظره على Threads in Java

مغلقرائج
بدأه زائر في 27 أبريل 2007 · 19 رد · 20,699 مشاهدة · في JavaSE
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم

1- definition of Thread

2- create Thread

3- start Thread

4- Thread life cycle

5- concurrency between threads

6- synchronized in thread

7- deadlock in threads

الbottom line في الThreads هو :

ليست لديك سلطه على كل شئ ، لابد أن تسلم بأنك لاتستطيع التحكم بكل شئ

Threads is vendor implementation اي بأنها لم تنشأ بلغة الجافا ، لذلك الجافا لاتصلح لـ real time systems

1- definition of Thread

Ooh ! :lol:

اقتباس
ما هو الــ Thread ؟

الثريد عبارة عن مهمة او وظيفة ممكن ان تعمل مع بعض بالتوازي Parallel

الجافا تستخدم الثريد في مواضع مثل:

1. Garbage collection : الذي يعمل في الخلفية لتوفير المساحة في الذاكرة.

2. الاحداث التي تحدث على الـ GUI مسئول عن تجميعها ثريد يعمل في الخلفية

الثريد يظهر وكانه يستخدم الـ Resources الخاصة به مثل CPU,Memory, Registers لكن مع مشاركة المتغيرات .

عادة الثريد يطلق عليه اسم Light Weight Process (LWP) لانه ينفذ مهمة تماما مثل الـ Process ولكن عادة تكون خفيفة

امثلة على استخدام الثريد :

من اوضخ الامثلة متصفح الانترنت نلاحظ مثلا ان المتصفح يحمل اكثر من صورة في نفس الوقت وذلك لان كل ثريد مسئول عن تحميل صورة مختلفة

ايضا نلاحظ ان النص يبدا بالظهور في نفس الوقت الذي تظهر فيه الصور وذلك لان هناك ثريد مسئول عن تحميل و عرض الـ HTML

ايضا تجد هناك تريد مسئول عن المدخلات من المستخدم..

تخيل حياتك من غير ثريد سوف تتطر الى انتظار كل صورة على حدة ثم ظهور النص و هكذا!

الـ JVM (multithreaded ) نفسها تستخدم الثريد فهي تشغل الثريد المسئول عن Garbage collection و ثريد المسئول عن تحديث GUI و ثريد لبرنامجك الذي يعمل تحت JVM

هل تريد مثال ؟

عندما تعمل برنامج عادي بلغة الجافا ، فإن البرنامج يعمل على thread اسمها main ،

class A{
	public static void main(String... args){
	   System.out.println(Thread.currentThread().getName());
	}
}

طيب حلو واضح وشلون نسوي thread :angry:

في البدايه دعونا نقوم بتفحص الامور ،

انت لاتريد أن تعمل thread ان تريد ان تستخدم thread في برنامجك ، انت فقط تريد job done by thread

ولا لا ؟

هل تريد ان تتحكم بالـ Thread ? ? Ooh

هل تريد ان تتحكم في كيفية اختيار thread من بين الـthread المنشأه حالياً ؟

ان اردت ذلك فما عليك سوى ان تذهب لjames gosling وتقول له اريد الكود المصدري للغة الجافا ، وستجده ومن ثم تعدل على ملايين الأسطر

لاتهتم سوف اساعدك ولكن بعدما اتخرج :lol:

2- create Thread

في الجافا هناك طريقه واحده لعمل thread اعيد وأكرر طريقه واحده

وهي ان تجعل الكلاس implements Runnable

Runnable هي interface لديها مثد واحده وهي public void run

class A implements Runnable {
   public static void main(String... args){
	   A a =new A();
   }
	public void run() {
		System.out.println("Hello world ");
	}
}

انت الآن اضفت صفه لجميع instances من الكلاس A بأنها Runnable أي بأنها hold in new Thread

طيب جميل أنت فعلت كل هذا ، والآن لديك Instance جاهزه وتريد تشغيلها ، لكن انت تعرف بأن تشغيل الثريد أو البدء في تشغيلها هي JVM specification

طيب مالحل ؟

الحل هو أنك تنشأ اوبجكت من الكلاس Thread فهو الكلاس الذي يتيح لك خيار تشغيل الثريد وتوقيفها ( لاننا اتفقنا بأنها ليست من تخصصك هذه الأمور )

class A implements Runnable {
   public static void main(String... args){
	   A a =new A();
	   Thread t=new Thread(a);
   }
	public void run() {
		System.out.println("Hello world ");
	}
}

جميل جداً ، الآن جعلت ماتريد في ثريد جديد ، ولكن حقيقة انت لم تعمل شئ !

انت فقط عرفت اوبجكت جديد من الكلاس Thread ، كيف اجعل الثريد تشتغل ؟

3- start Thread

class A implements Runnable {
   public static void main(String... args){
	   A a =new A();
	   Thread t=new Thread(a);
	   t.start();
   }
	public void run() {
		System.out.println("Hello world ");
	}
}

الآن انت انسان رهيب وعجيب ، انشأت thread جديده واشتغلت وعملت فيها ماتريد (هنا طباعة Hello world )

نكمل بالليل ياشباب اصلي عاوز اروح للـVS 2005 :blink:

#2

الله عليك يااستاذ عيسى .. موضوع مهم جداا وكنت محتتاجه ..كثير

وراح نضل معك الى النهايه في الكلام على هذا الموضوع ... وشرحه

ونحن بالانتظار ..

لاتتأخر :)

تحياتي لك ..

#3

شباب وشلونكم ، الامور واضحه ؟

نكمل يعني ؟ :unsure:

في البدايه احب اوضح شي ،

الكثير يعمل thread بهذه الطريقه :

class A extends Thread  {

	//your stuff 

}

نعم انت تستطيع ان تفعل هذا وتنشأ thread وتفعل كل ماتريد ، ولكنك تناسيت أمرين ، الأمر الأول هو عندما تقول أن A subclass from Thread

فمعناتها انك تريد أن تجعل class A بمواصفات خاصه من Thread class بمعنى انك تريد أن تعمل overriding للمثد الموجوده في class thread وانت لاتستطيع هذا لأنها JVM specifiation ،

الأمر الآخر هو أن Runnable interface أتت لميزه في لغات OO وهي declare to interface يعني انت تريد اضافة صفة Runnable على الكلاس حقك ،

هذين الامرين بجانب أنك لاتستطيع عمل extends اكثر من مره في الجافا وأن Thread class نفسه هو implements Runnable

اوكي وش رايكم نلمح لمحه سريعه عن الThread class :

 Thread(Runnable a)

وهذا هو Constructor الذي نستخدمه في هذا الموضوع ، يستقبل runnable instance ويعمل اوبجكت من thread

 String getName()

يرجع اسم الthread فعندما تعمل thread وتود معرفة اسمها تطلب هذه الداله ، علماً بان اسماء الثريد ببرنامج by default هي thread-i حيث i هو رقم

 boolean isAlive()

اذا كنت تريد معرفة الى اين وصلت thread حقتك ، وهل هي live أم لا ، استعمل هذه المثد

sleep( int millesecond )

لغرض ما ، أنت تريد ان تجعل هذه الthread ان تتوقف لمده معينه ، استعمل هذه المثد

start()

لبداية الthread وجعل JVM يشغل المثد run بالأصح يشغل عمل الthread ، استعمل هذه المثد

ملاحظه / تستطيع استدعاء المثد run ولكن ستكون كأي مثد أخرى ستنادى من الثريد الحالي main thread ولاتكون thread جديد في هذه الحاله

لاتستطيع استدعاء المثد start أكثر من مره ،

مثال /

class A implements Runnable {
	public static void main(String ... args){
		A a=new A();
		Thread t=new Thread(a);
		t.start(); // please run this thread
		System.out.println(t.getName()); //Thread-0
		System.out.println(t.isAlive());// true t alive 

	}

	public void run() {
		try {
			System.out.println("Hello guys ");
			Thread.sleep(10000); // please sleep for 10 second 
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}


	}



}

نكمل بعد شوي :lol:

#4

4- Thread life Cycle

حلو حلو ، خلونا نشوف هالصوره :

thread_life_cycle.jpg

سأشرح أهم مافيها ،

الحاله born :

هي الthread instance قبل ماتنادي start method

فهو اوبجكت موجود ولكن ليس thread حقيقي حتى الآن

class A implements Runnable {
	public static void main(String ... args){
		A a=new A();
		Thread t=new Thread(a); 

	}
	 public void run() {

	}}

ready or runnable state :

وهي حالة الthread بعد استدعاء start method

class A implements Runnable {
	public static void main(String ... args){
		A a=new A();
		Thread t=new Thread(a); // born state 
	   t.start(); // ready state
	}
	 public void run() {

	}}

running

هي الحاله الثريد عند دخوله المثد run فهو running الآن ، ولايمكننا التنبؤ بمتى تتم هذه الحاله ، فهي مسؤولية الscheduler ضمن JVM

class A implements Runnable {
	public static void main(String ... args){
		A a=new A();
		Thread t=new Thread(a); // born state 
	   t.start(); // ready state
	}
	 public void run() {
		 System.out.println("Wow ");// thread which execute this statement is running thread 
	}}

blocked state :

عندما الثريد تريد ان تقوم بقرائة من ملف او اي عملية I\O ،

فهي تتحول إلى هذه الحاله ، تنتظر الresource الى ان يتاح لها وتقوم بالعمليه . وترجع إلى الحاله ready بعد اتمام العمليه

dead state :

هي حالة الثريد بعد تنفيذ المثد run فهي تنتهي بنهاية هذه المثد ولايصبح لها وجود ،

wait state :

في الجافا ، كل اوبجكت ننشأه له lock خاص فيه ، هذه حالة الثريد عندما يريد ان يطلب اي أوبجكت ولم يحصل lock حقه بمعنى آخر هناك thread أخرى لديها الlock حق هذا الاوبجكت

فإن هذا الثريد يتحول إلى waiting state إلى ان يقوم الثريد الأول بعمل realease للlock ويتحصل عليه الثريد ، في حالة طلب المثد wait من الثريد فهي تكون في هذه الحاله حتى يعمل أحد الثريد الأخرى notify لها ، ثم تذهب إلى ready state مره أخرى

sleep state :

هي حالة الثريد بعدما نعمل sleep مثد عليها ، فهي في حالة سبات إلى ان ينتهي الوقت المحدد في المثد ، وبعد ماينتهي الوقت تذهب إلى ready state مره أخرى

يمكن تلخيص الthread life cycle في أربع حالات :

ready

running

blocked

dead

#5

ماعليش يااخوان انشغلت شوي وراجع لكم ان شاء الله

#6

مارأيكم بعمل أمثله على ماتعلمنا ،

مارأيكم باستخدام أكثر من thread :

class A implements Runnable{
	public static void main(String ... args) {

		A a=new A();
	   Thread thread1=new Thread(a);
	  Thread thread2=new Thread(a);
	  thread1.setName("Saad");
	  thread2.setName("Khaled");
	  thread1.start();
	  thread2.start();
		}

	public void run() {
			for(int i=0;i<5;i++)
		  System.out.println(Thread.currentThread().getName());
		}
}

لاحظ المخرجات زين ، المخرج لاتستطيع توقعه ، output is not predictable

لايعني أنك قلت في برنامج بأن thread1 تشتغل قبل thread2 بأن هذا سيحصل ، ليس بالضروره ، فهذا يعتمد على dispatcher or scheduler هو الذي يحدد أي الثريد يختار ،

إن خرج لك مخرج كنت تتوقعه أعد تشغيل البرنامج كذا مره ، ستجد أنه يختلف ، هذا لأن المخرجات غير متوقعه أو متنبأه ، ولاتعني المثد start بأن هذا الثريد سيشتغل قبل الآخر

مارأيكم لو نضيف sleep للداله ؟

class A implements Runnable{
	public static void main(String ... args) {

		A a=new A();
	   Thread thread1=new Thread(a);
	  Thread thread2=new Thread(a);
	  thread1.setName("Saad");
	  thread2.setName("Khaled");
	  thread1.start();
	  thread2.start();
		}

	public void run()  {
			for(int i=0;i<5;i++){
		  System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(2000);
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}}
		}
}

لاحظ التوقف لمدة ثانيتين بين طباعة الجمل

ياجماعة الخير اذا فيه شي مو واضح ، مافي مشكل there is no problem :lol:

لأننا سنبدأ بأكثر من هذا فاللي مو واضح عنده شي يقوله

#7

جيد الآن عرفنا كيف ننشأ threads وكيفية انتقال thread

نذهب إلى synchronizing في الجافا ،

sunchronized هي keyword في الجافا ، مهمتها الضمان على عدم دخول سوا thread واحده at atime

يمكن تطبيقها على method أو على block

class A implements Runnable {
	public  void run() {
		go();
		}
	public synchronized void go(){
		// guarantee only one thread enter this method at atime 
		}

}

ماذا لو كنت تعمل على اوبجكت أو على مثد من صن ليست synchronized

كيف تضمن انه لايوجد سوى thread واحده تدخلها ؟

class A implements Runnable {
	String s="Oops";
	public  void run() {
		go();
		}
	public  void go(){
	  synchronized(s){
	  s=s.concat(" Aha ");
	  }
	}

}

في كل الأمرين الهدف هو : منع أكثر من thread الدخول بنفس الوقت إلى الاوبجكت المحدد

طيب مالهدف من هذا ؟ :blink:

الهدف هو عدم وجود معلومه رديئه ،

تخيل ان لديك الكلاس التالي :

class Account{
	private double  budget;
	void withdraw(int a){
		if(budget>=a) budget-=a;
	}
	//.other utility classes 
}

لديك اثنان يريدان سحب قيمة 100 ريال من الحساب ، والحساب حالياً budget=100

لديك two threads سيدخل الاول وليكن اسمه خالد ، وفي اثناء دخوله دخل الثاني وليكن اسمه سعد

الآن خالد وسعد كلهم متأكدين بان هناك قيمة بالحساب وسوف يقومان بسحب 100 ريال ، لكن المشكله انه عندما سحب خالد اصبح الرصيد صفر ، ولكن سعد توه داخل والنظام قايل له اوكي هناك رصيد سوف اسحب لك ، يتفاجأ سعد بان النظام يقوله لايوجد رصيد كافي ؟؟ والنظام قايله قبل شوي إنه يوجد رصيد

مالحل ؟

الحل هو انه عندما تدخل أي thread في المثد withdraw نجعل الرصيد ملك لهذه الthread وهو في مثالنا خالد

اي ثريد أخرى ( مثل سعد ) تريد ان تعمل withdraw نجعلها في waiting list وعندما ينتهي خالد نعمل nofity لها

synchronized withdraw(int i) {//plaplapla
}

كل اوبجكت في الجافا له lock خاص فيه ، لذلك اول مايدخل خالد synchronized method يأخذ الـlock لهذا الاوبجكت Account

ولايحق لأي أحد آخر سواءاً سعد أو أي thread أخرى ان تدخل أي synchronized block ، ولكن لها الحق ان تدخل any non-synchronized block

وبهذا تأكدنا أن سعد لن يرى قيمه ليس لها معنى ،

#8

اللي مافهم الsynchronized يتخيل معي السيناريو التالي :

تخيل بأنه يوجد في بيتك 3 غرف ، غرفتين مغلفه بنفس المفتاح والغرفه الثالثه مفتوحه

الآن لديك انت المفتاح ، فبالتالي باستطاعتك الذهاب لهذين الغرفتين ، ومن ليس لديه المفتاح لايستطيع الذهاب سوى للغرفه الثالثه المفتوحه

فبالتالي لن تخاف على مابداخل هذه الغرف لأن المفتاح معك ، انت الآن ضمنت بأنه لاأحد سيدخل هذه الغرف إلا ان اعطيته المفتاح ،

هذه هي فكرة synchronized بأنك تغلق جميع synchronized method التي تستخدم نفس الاوبجكت ، فهي فقط للثريد الحالي الذي يمتلك object lock

اما الغرفه الثالثه فهي مثد بدون synchronized فالجميع باستطاعته الدخول لها في أي وقت ،

وضحت ياشباب ؟ :blink:

#9

deadlock

هي حاله مزعجه تأتي أحياناً عندما يكون لديك two threads or more

تخيل بأن لديك thread-A and thread-B

الآن thread-A دخلت في synchronized block معناتها أن thread-A لديها lock حق الاوبجكت ولاتستطيع أي ثريد أخرى الدخول لهذا البلوك ،

فلو جائت thread-B تبي نفس الاوبجكت سوف تنتظر إلى ان thread-A يخرج ويعمل realease للـ lock

المشكله هنا ، ماذا لو كانت thread-A تريد الاوبجكت اللي مالكه الآن thread-B ، ( بالتأكيد سوف تذهب إلى waiting list) ،

لكن بالمقابل thread-B هي تريد الاوبجكت اللي ماله thread-A ، فكل واحد منهم ينتظر الآخر ان يفك اللوك ، حتى يأخذه ، ومستحيل يتقابلون لأن كلهم waiting وينتظر الآخر يفك حتى يأخذ اللlock ،

مثال

 class A implements Runnable {
	String  s1 = "Oops";
	String  s2 = "Aha !!"; 
  public static void main(String... args) {
	  A a=new A();
   Thread t1=new Thread(a);
   Thread t2=new Thread(a);
   t1.setName("Saad");
   t2.setName("Khaled");
   t1.start();
   t2.start();
  }
 public void run(){
	  synchronized (s1) { // get s1 lock
		  System.out.println(Thread.currentThread().getName());
		  synchronized (s2) { // get s2 lock 
		  System.out.println(Thread.currentThread().getName());
		  }}
			 synchronized (s2) { // get s2 lock ( maybe deadlock here) 
		  System.out.println(Thread.currentThread().getName());
				 synchronized (s1) { // get s1 lock (maybe deadlock here )
				 System.out.println(Thread.currentThread().getName());
				 }}
	  }
 }

شكلي اكلم روحي :lol:

تم تعديل هذه المشاركة بواسطة {عيسى} في 28 أبريل 2007 في 21:54

#10

هههههههههههههه

لا تكلم روحك ولا شي .. بالعكس متابعين على الصامت :blink:

الصراحه الشرح ولا أحلى ..

طيب اوكي بالنسبه للميثود

join () // to stop() 

suspond()
resume()

/*
* كيف عملها .. او توضيح أكثر  فيها
*
*/

:rolleyes: الله يعطيك العافيه ..استاذي

#11

join

هي تستخدم عندما يكون لديك thread يعتمد على عمل thread آخرى ،

عندما تقول :

thread1.join()

فانك تقول : اربط الcurrent thread في آخر الثريد thread1 ،

بمعنى بعد ان تنتهي thread1 قم بتشغيل current thread

بقية المثد التي اشرت لهن deprecated لم تعد تستعمل ، فقد ألغيت لأسباب متعدده

#12

صحيح نسبياً كلامك ، في اي وقت sheduler يضمن بأن الcurrentThread هي أكبر او مساويه للready threads

لكن هذا غير مضمون إلى درجه كبيره ، ولايجب ان تعتمد عليه فيه برنامج فهو مضمون ( نسبياً )

synchronized عندما اكثر من ثريد تريد الدخول إلى نفس resource ، سواءاً بنفس الاولويه أو أولويه مختلفه

#13

جزاك الله خيرا

يا نفس كفي عن العصيان واكتسبي ----- فعلا جميلا لعل الله يرحمني

#14
اقتباس
في الجافا هناك طريقه واحده لعمل thread اعيد وأكرر طريقه واحده

وهي ان تجعل الكلاس implements Runnable

هل متأكد إنها طريقة واحدة لأنى اقدر اعمل Extend لل Thread Class ؟

لكن هى الطريقة المفضلة لأنى إذا Extend ال Thread Class فمش هقدر اشتق اى Class تانى ، فهنا بتيجى ميزة انى استخدم ال Interface

غير كدا الموضوع لاغبار عليه .. وشكرا لك على المجهود الطيب.

#15

برمجياً بالطريقتين تقدر

لكن انا كنت اتحدث من وجهة نظر Object Oriented فقط

وإلا في كل الحالتين الكود صحيح وسينفذ

شكراً لك أخي Striker X

#16

أخ عيسى يجزاك الله الخير

كيف ممكن أستخدم wait and notify

حاولت أن أتعلمها ولكني وجدت نفسي أتجه إلى susbend و resume

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

#17

أهلا أخي علاء

ساضع موضوع يكون عملي أكثر على Thread Synchronization

wait & notify تستخدم داخل synchronized code بالأصح لايمكن مناداتهن إلا اذا كنت تملك lock الاوبجكت

عندما يوجد لديك two thread وجميعهن يشتركن في اوبجكت واحد

لنفرض ان لديك اوبجكت بسيط عباره عن عدد

وهذا الاوبجكت مشترك بين خيطين ،

الخيط الاول يقوم بالتحقق من العدد ويقوم بعمل فلتر له

والخيط الثاني يقوم بعمل عمليه رياضيه معقده

فبعد ان تقوم بالتحقق عليك ان تعطي الاوبجكت للخيط الثاني لإكمال العمليه الرياضيه

وكذلك بعد ان تقوم بعمل العمليه الرياضيه عليك ان تعطي الاوبجكت للخيط الاول للتحقق وعمل Filtering للعدد الحالي

هذه فكره سريعه على عمل wait & notify

suspend & resume الغيت ،

public class Main {

	public static void main(String [] args){
	  new Target();
	}
}
class A{
   public int value=0;
}
class Target {
  A a=new A();
	Target(){
		Thread thread1=new Thread(new Check());
		Thread thread2=new Thread(new Increment());

		System.out.println("Start Threads !!");
		thread1.start();
		thread2.start();

	}

 class Check implements Runnable {
	public void run() {
		synchronized(a){
			for(int i=0;i<5;i++)
			{
					try {
						System.out.println("value is "+a.value);
						a.wait();
					} catch (InterruptedException ex) {
						ex.printStackTrace();
					}
				  a.notify();  
			}
	   }

	}

}
class Increment implements Runnable {
	public void run() {
		synchronized(a){
			for(int i=0;i<5;i++){
			a.value++;
			System.out.println("Increment 1");
						a.notify();
				try {
					a.wait();
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}

					}			
		}

	}

}}
#18

في البداية أخ عيسى أريد أن أستفسر

أولاً أنت لم تقم بإنشاء كائن اسمه a إلا في الفئة Target

كيف استخدمته في check و Increment هل من المفروض أن تمرر الكائن للـ Thread

شكراً لك على المثال

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

#19

الكلاس Increment & check هي كلاسات داخليه ، داخل الكلاس Target فلذلك لهم اكسس على كل members للTarget

في الويت والنتوفاي ، لابد ان يشترك اوبجكت ين اكثر من ثريد

وطريقة الاشتراك كما تحب ولكنني كنت مستعجل وكتبتها على شكل كلاسات داخليه

هذه طريقه أخرى :

\public class Main {

	public static void main(String [] args){
	  new Target();
	}
}
class A{
   public int value=0;
}
class Target {
  A a=new A();
	Target(){
		Thread thread1=new Thread(new Check(a));
		Thread thread2=new Thread(new Increment(a));

		System.out.println("Start Threads !!");
		thread1.start();
		thread2.start();

	}
}
 class Check implements Runnable {
	 A a;
	 Check(A a){
		 this.a=a;
	 }
	public void run() {
		synchronized(a){
			for(int i=0;i<5;i++)
			{
					try {
						System.out.println("value is "+a.value);
						a.wait();
					} catch (InterruptedException ex) {
						ex.printStackTrace();
					}
				  a.notify();  
			}
	   }

	}

}
class Increment implements Runnable {
	A a;
	Increment(A a){
		this.a=a;
	}
	public void run() {
		synchronized(a){
			for(int i=0;i<5;i++){
			a.value++;
			System.out.println("Increment 1");
						a.notify();
				try {
					a.wait();
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}

					}			
		}

	}

}
#20

شكراُ لك أخ عيسى

لم أنتبه إلى أنك قصدت فئات داخلية لأنك كنت قد قسمت الشيفرة إلى أٌسام

الله معك

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

هذا الموضوع مغلق.

مواضيع مشابهة