www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: Idea for Threads

reply Martin Persenius <martin persenius.net> writes:
Thomas Kuehne Wrote:
 Most likely some restrictions are missing but this should give you an
 idea.
 
 Thomas

I have been thinking about this a bit and have a couple of ideas to play with. I want to test one with you. One of the problems is memory corruption because reading is done when a write is in progress. This can be avoided by the use of mutexes. I think this should be automated through the use of protected types, which would give the programmer less to think about. The lock needs to be effective for both reads and writes, but only writes need to lock. Already objects have a mutex upon creation (read it in a post from 2006), this could be used for the protected types as such: 1. Before reading, check if it is locked, then wait or read. 2. Before writing, acquire lock, write, release. The protected types could be the regular names suffixed with _p (e.g. uint_p. Now, this doesn't solve all issues that could result from the use of pointers, but if you just avoid that, automatic locking would at least simplify the matter. I think this sounds like a pretty straightforward idea, so I'd be happy to hear any outstanding objections. (it would benefit from having structs capable of opAssign, or language integration) Example which always locks... not as fast as only checking lock on read: private import tango.io.Stdout, tango.util.locks.Mutex; class Protected(T) { private T item; Mutex m; this() { m = new Mutex(); } void opAssign(T v) { m.acquire(); scope(exit) m.release; item = v; } T opCall() { m.acquire(); scope(exit) m.release(); return item; } } int main() { auto x = new Protected!(int); x = 5; Stdout.formatln("x.item = {0}", x()); return 0; }
May 13 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Martin Persenius wrote:
 1. Before reading, check if it is locked, then wait or read.
 2. Before writing, acquire lock, write, release.

That's not safe, there's a race condition. Thread A: Check if locked, begin reading.
 thread switch <<<



 thread switch <<<



Thread A will still have the stuff being read changing from under it... The writing procedure needs to be modified to check if anyone's currently reading and, if so, wait until they're done. This means the readers also need to mark something while they're busy. It'd have to be some kind of counter since multiple simultaneous readers are allowed. If you want new readers to wait until a waiting writer has done it's thing the readers also need to actually lock something, though perhaps only at the beginning and end, not while they're working. Some googling reveals that pthreads has pthread_rwlock* to implement this[1]. [1]: See http://www.die.net/doc/linux/man/man3/pthread_rwlock_init.3.html and related pages.
May 13 2007
parent reply Martin Persenius <martin persenius.net> writes:
Frits,

You (I) learn something new everyday - thanks!

What do you think about types with automatic locking then? Not specifically my
fouled up attempt.

I suppose I need to become more familiar with the exact details of the problems
to be overcome in threading.

Martin
May 13 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Martin Persenius wrote:
 Frits,
 
 You (I) learn something new everyday - thanks!

You're welcome.
 What do you think about types with automatic locking then? Not specifically my
fouled up attempt.

It's a nice idea, but probably not easy to implement in a way that's intuitively "right". If the type has multiple fields, for example, you might want to keep the lock over multiple accesses. I'm not sure if that can be done nicely in the current language. If "smart references" (akin to C++ "smart pointers") were possible that could be a good way to implement it though. But overloading "." is currently not possible...
 I suppose I need to become more familiar with the exact details of the
problems to be overcome in threading.

I'm not terribly familiar with them either. I just noticed a race condition :).
May 13 2007
prev sibling parent reply "Craig Black" <cblack ara.com> writes:
 One of the problems is memory corruption because reading is done when a 
 write is in progress. > This can be avoided by the use of mutexes.

I was thinking about this. An efficient mutex implementation should take into consideration read and write access. If there is a write, then all access to the data should be prohibited until the write is completed. However, any number of reads should be able to work together in parallel. If a read is treaded the same as a write, then that would be very inefficient. I don't know a lot about the details of mutexes. Do they multiple reads simultaneously? -Craig
May 14 2007
parent reply Sean Kelly <sean f4.ca> writes:
Craig Black wrote:
 One of the problems is memory corruption because reading is done when a 
 write is in progress. > This can be avoided by the use of mutexes.

I was thinking about this. An efficient mutex implementation should take into consideration read and write access. If there is a write, then all access to the data should be prohibited until the write is completed. However, any number of reads should be able to work together in parallel. If a read is treaded the same as a write, then that would be very inefficient. I don't know a lot about the details of mutexes. Do they multiple reads simultaneously?

ReadWrite mutexes do, but they're a bit more complicated than your average mutex. Sean
May 14 2007
parent Downs <default_357-line yahoo.de> writes:
Sean Kelly wrote:
 Craig Black wrote:
 I don't know a lot about the details of mutexes.  Do they multiple 
 reads simultaneously?

ReadWrite mutexes do, but they're a bit more complicated than your average mutex.

About like so? class extlock { Thread writing=null; int reading=0; int wfwl=0; /// waiting for write lock private bool lock(bool exclusive)() { synchronized(this) { static if (exclusive) if (writing||(reading>0)) return false; else { writing=Thread.getThis; return true; } else if (writing||wfwl) return false; else { reading++; return true; } } } void write_lock() { synchronized(this) wfwl++; while (!lock!(true)) rest; synchronized(this) wfwl--; } void read_lock() { while (!lock!(false)) rest; } // the asserts are unsynced because they're not part of the normal flow // and don't strictly need to be threadsafe. void write_unlock() { assert(writing==Thread.getThis); synchronized(this) writing=null; } void read_unlock() { assert(!writing); assert(reading>0); synchronized(this) reading--; } } scope class readlock { extlock s; this(typeof(s)s) { this.s=s; s.read_lock; } ~this() { s.read_unlock; } } scope class writelock { extlock s; this(typeof(s)s) { this.s=s; s.write_lock; } ~this() { s.write_unlock; } } unittest { auto sync=new extlock; assert(sync); sync.read_lock; sync.read_unlock; { scope wl=new writelock(sync); } }
May 14 2007