here is some psuedo code to express my idea, but let me know if this
idea is wrong or not!
=====
NSMutalbeDictionary* fileSemaphors = [[NSMutableDictionary alloc]
init];
...
...
WriteFile(NSString*)filename
{
NSObject* mutex = [fileSemaphors objectForkey:filename];
if (mutex == nil)
{
mutex = [fileSemaphors addObject:filename withkey: filename];
}
@synchronized(mutex)
{
writethefile();
}
}
DeleteFile(NSString*)filename
{
NSObject* mutex = [fileSemaphors objectForkey:filename];
if (mutex == nil)
{
mutex = [fileSemaphors addObject:filename withkey: filename];
}
@synchronized(mutex)
{
deletethefile();
}
}
if ( [myLock tryLock] == YES )
{
writethefile();
[myLock unlock];
}
else bail();
if ( [myLock tryLock] == YES )
{
deletethefile();
[myLock unlock];
}
else bail();
ALWAYS ALWAYS ALWAYS
Unlock at the end of the atomic code block.
or suffer. Terribly.
hrm, but I wanted it to lock only on that specific file, because it
would be ok for me to write to a different file and delete a different
file. Also, whats the difference between synchronized and nslock?
Thanks.
@synchronized(object) {
...
}
This will lock on "object", meaning that any other threads which hit a
@synchronized directive with the same object will not proceed until
this thread has exited its @synchronized block.
NSLocks are simply Object-Oriented flags that you can lock, unlock or
try to lock
They leave it up to you what to do with them.
[myLock lock]; locks the lock. This prevents other threads that are
trying to acquire the lock from continuing until the lock is released.
(That is, if they have a [myLock lock]; line that thread stops there
until the first thread releases (unlocks) mylock. )
[myLock unlock]; unlocks the lock. This can only be done by the thread
that locked the lock, and obviously allows other threads to acquire
it.
[myLock tryLock]; attempts to lock the lock, returns NO if it fails,
otherwise returns YES. (does not stop the thread )
[myLock lockBeforeDate:]; attempts to lock until a specified date. NO
on failure, otherwise YES. (use to time out and resume a thread)
Foundation classes for Files at the level you are asking about are 3:
NSFileManager - move files, copy files, delete files, check if files
exist, and get the attributes of files.
NSFileHandle - read and write data to and from a file (or other
stream, such as stdin, stdout, stderr, etc.). Some Foundation classes
have their own file writing methods and some are thread safe and some
are not.
NSWorkspace - Part of the AppKit Framework
OS X being based upon BSD allows multiple openings of the same file
for writing
simultaneously, but I am almost sure attempting to delete a file open
for writing
would fail. Exclusive access to open files is OS/version specific, and
I believe it depends upon the flags set upon opening the file. (see
docs)
Yeah , its complicated.
A simpler method may be to generate a lockfile for each file you
open
named <filename>.lock in the same dir as <filename> and containing the
PID of the process that created the file. (This , my son, is the unix
way ...) check if it exists, and if so, don't delete or modify the
file. Delete the lockfile when you are done with it (close the file or
release it for the time being) , then you may delete the file.
A safe way to deal with files is to always assume failure and deal
with it
first and if it succeeds great.
if ( File_Operation_Fails ) { dealWithThefailure(); }
else { Use_it(); }
This is in opposition to modern OO that relies on exceptions to be
passed off or up the Object hierarchy after a failure is "caught". But
then there's nothing modern about a unix C file structure or how you
create, destroy, read and write to files. Preventing faults like
checking before attempting to write or delete is fundamentally safer.
Exceptions are for (cough) exceptional cases. To use them for super
common occurrences like FileNotFound, FileIsAlreadyOPen,
FileIsReadOnly, ... I think
is a bad programming paradigm. You end up with a bunch of hyper-
specific exception opjects that aid systems debuggers but not regular
programmers so much, and large , slow code. Exceptions Considered
Harmful.
Try/Catch blocks are essentially leap first, throw-a-rope-to-try-
to-save-yourself-afterward methods. Programmers like them because you
can tack on fixes to code when it should actually be reworked to be
safer (but that can be a brain-twister.) Also it seems more positive
to get something working first and handle the failure conditions
second, but it is also more dangerous, as it allows her to
write unsafe code and try to "safen" it up later.
1 2 3 4 I will start a flame war.
good luck and let us know how it goes.
[Final thought] "Attack of the Killer File-Deletion Thread".
It seems to me that a thread being an execution "Worker object" should
be responsible for deleting files it creates and freeing resources it
reserves.
The producer/consumer model is simpler if the consumer of the file has
control of it but encapsulation is broken. Depends on the use you put
the files to I guess.
> NSObject* mutex = [fileSemaphors objectForkey:filename];
> if (mutex == nil)
> {
> mutex = [fileSemaphors addObject:filename withkey: filename];
> }
You must synchronize this, too. Two threads might add the mutex to your
array at the same time!
WriteFile(NSString*)filename
{
NSObject *mutex;
@synchronized(fileSemaphors) {
mutex = [fileSemaphors objectForKey:filename];
if (mutex == nil)
{
mutex = filename;
[fileSemaphors setObject:mutex forKey:filename];
}
}
@synchronized(mutex) {
writethefile();
}
}
DeleteFile(NSString*)filename
{
NSObject *mutex;
@synchronized(fileSemaphors) {
mutex = [fileSemaphors objectForkey:filename];
if (mutex == nil) {
mutex = [fileSemaphors addObject:filename withkey:filename];
}
}
@synchronized(mutex) {
deletethefile();
}
}
One problem that remains is that you never remove the filename from
fileSemaphors. You can't do so in or after the last synchronized block
in DeleteFile as some other thread might know the mutex object and this
thread would enter the synchronized zone after you removed the mutex
object from the dictionary while a third thread would re-add a new mutex
object for the filename.
If you have to remove the object from the dictionary (because your list
of possible filenames is not limited), you could implement a kind of
reference counting and remove only for the last thread that wants to
remove the mutex.
An option to synchronized could be opening the file read only, calling
flock on the handle and closing when you are done. Note that deleting
open files works on Unix.
Bye,
Daniel
Hi Daniel,
I see what you mean about locking on the file semaphors dictionary.
About removing the file semaphor, I dont really need to do that.
However, eventually I would have to release the entire dictionary
object. I would assume that that has to be synchronized as well? But
what to do if the object I'm releasing is the also used as the mutex
(which your code suggestion is using?)
WriteFile(NSString*)filename
{
NSObject *mutex;
@synchronized(fileSemaphors) {
mutex = [fileSemaphors objectForKey:filename];
if (mutex == nil)
{
mutex = filename;
[fileSemaphors setObject:mutex forKey:filename];
}
}
@synchronized(mutex) {
writethefile();
}
}
DeleteFile(NSString*)filename
{
NSObject *mutex;
@synchronized(fileSemaphors) {
mutex = [fileSemaphors objectForkey:filename];
if (mutex == nil) {
mutex = [fileSemaphors addObject:filename withkey:filename];
}
}
@synchronized(mutex) {
deletethefile();
}
}
[destructor]....
@synchronized(filesemaphos)
{
[filesemaphores release];
}
what would happen if the delete thread is still running and tries to
synchronize on a locked object? I can use some other object to
synchronise, but when do I know to release it? It sounds like I have
to wait for all the threads to finish before releaseing the mutex
object....
> I see what you mean about locking on the file semaphors dictionary.
> About removing the file semaphor, I dont really need to do that.
> However, eventually I would have to release the entire dictionary
> object. I would assume that that has to be synchronized as well? But
> what to do if the object I'm releasing is the also used as the mutex
> (which your code suggestion is using?)
>
>[...]
>
> [destructor]....
>
> @synchronized(filesemaphos)
> {
> [filesemaphores release];
> }
>
> what would happen if the delete thread is still running and tries to
> synchronize on a locked object? I can use some other object to
> synchronise, but when do I know to release it? It sounds like I have
> to wait for all the threads to finish before releaseing the mutex
> object....
You have to synchronize in this case, too. And you must not release the
objects until all threads are done with them.
There is a simple solution that does not work in all cases: Retain the
filename when a thread gets it and release it after you released the
mutex on it. This way, if there is a thread working on the mutex
(filename) when you destruct the filesemaphores object, the filename
will stay valid until the thread does no longer care.
There is still a problem if you destruct the semaphores list before a
thread has acquired its lock on the filename it wants to work on. Also,
your threads will continue to run on an object that has already been
destructed! This is not a problem as long as the methods writethefile
and deletethefile do not access anything but the filename (they retained
it). But it might be a problem if some of the threads have not reached
this methods yet.
So you should know wether the threads need members of the object you
destruct and, if so, make sure all threads finished before you call
dealloc. Not calling dealloc early might be as easy as retaining the
object for all threads (before starting the thread as you do not want to
dealloc the object while one thread is about to start) and releaseing
them at the threads end. However I am not sure wether retaining in one
thread and releaseing in an other is allowed for NSObjects. If it is (as
I expect), the last thread that exits will actually release the object.
Bye,
Daniel