GtkD code review - How to update a progressbar using data sharing concurrency

adnan338 relay.dev.adnan at protonmail.com
Sun Jun 21 12:52:24 UTC 2020


On Sunday, 21 June 2020 at 12:43:32 UTC, adnan338 wrote:
> On Sunday, 21 June 2020 at 09:16:06 UTC, Ali Çehreli wrote:
>> On 6/20/20 9:30 AM, adnan338 wrote:
>>
>> > Hello, I need a code review on my strategy
>>
>> I don't know gtkd so I did not compile the code and I did not 
>> review the code very carefully.
>>
>> However, I don't think you need to 'synchronized' the whole 
>> parallel loop. Since there is only one thread that executes 
>> start(), that synchronized cannot have any effect at all. What 
>> you want to synchronize is the mutating access to 'completed' 
>> by the threads that parallel() starts automatically. So, move 
>> 'synchronized' just around that expression:
>>
>> // REMOVE this one:
>> // synchronized
>> // {
>>
>>   foreach (_; downloader.links.parallel())
>>   {
>>       Thread.sleep(uniform(0, 6, rnd).seconds());
>>
>>       // ADD this one:
>>       synchronized {
>>         ++cast() downloader.completed;
>>       }
>>   }
>>
>> // }
>>
>> Ali
>
> Thank you. However I am concerned about a data race. I have at 
> least two places where I am at risk of a data race.
>
> 1. In the static method `start`, where I mutably access the 
> value `completed` from parallel threads.
>     I *think* I have implemented it safely simply by making the 
> `completed` a shared(size_t).
>
> 2. In the `timeout` delegate. The glib Timout is a struct that 
> accepts a delegate and invokes it periodically. The ctor 
> requires 3 values,
>   i. polling time (in ms)
>   ii. the delegate to execute in each polling
>   iii. priority
>   The return value is whether the timeout should continue. The 
> gtk event loop executes the triggers timeout automatically 
> afaik.
>
>     I think I am at risk here. Before constructing the Timeout, 
> I create a new task and invoke it in a new thread:
>
> auto downloadTask = task!(Downloader.start)(downloader);
> downloadTask.executeInNewThread();
>
> Right after that I create a Timeout. I try to read the 
> `completed` (which is the shared member as mentioned in the 
> previous point) once in every 100 ms.
>
> auto timeout = new Timeout(100, delegate bool() {
> 			if (downloader.completed < downloader.links.length)
> 			{
> 				if (downloader.completed == 0)
> 				{
> 					pib.setText(`Awaiting response...`);
> 					pib.pulse();
> 				}
> 				else
> 				{
> 					pib.setText(`Downloading...`);
> 					pib.setFraction(downloader.getFraction());
> 				}
> 				return true;
> 			}
> 			else
> 			{
> 				super.setTitle(`Downloading complete`);
> 				// pib.setShowText(false);
> 				pib.setVisible(false);
> 				return false;
> 			}
> 		}, GPriority.HIGH);
>
> I am thinking I am prone to a data race here. The `completed` 
> is being updated by the `start` method and also is being read 
> by the main thread inside `timeout`
>
> I am trying to figure out how to prevent this data race.

It is also worth noting that simply saving the values from a new 
sync block makes the `completed` remain stuck in 0;

auto downloadTask = task!(Downloader.start)(downloader);
		downloadTask.executeInNewThread();
		size_t currentlyCompleted;
		double currentFraction;
		synchronized
		{
			currentlyCompleted = cast(size_t) downloader.completed;
			currentFraction = cast(double) downloader.getFraction();
		}

		auto timeout = new Timeout(100, delegate bool() {
			if (currentlyCompleted < downloader.links.length)
			{
				if (currentlyCompleted == 0) // --- Stuck here! ---
				{
					pib.setText(`Awaiting response...`);
					pib.pulse();
				}
				else
				{
					pib.setText(`Downloading...`);
					pib.setFraction(currentFraction);
				}
				return true;
			}
			else
			{
				super.setTitle(`Downloading complete`);
				// pib.setShowText(false);
				pib.setVisible(false);
				return false;
			}
		}, GPriority.HIGH);


More information about the Digitalmars-d-learn mailing list