"Linux Multi-process sync" record lock __linux

Source: Internet
Author: User
Tags flock flock lock mutex sleep

the main reference to the http://blog.csdn.net/anonymalias/article/details/9197641 (anonymalias column)


A record lock is an extended type of read-write lock in thread synchronization and can be used to synchronize file reading and writing with a relative or unrelated process , and to perform a lock operation by fcntl function. Although read-write locks can also be synchronized by processes in a shared memory area, fcntl record locks are often easier to use and more efficient.


There are also two types of locks: Shared read locks (F_RDLCK) and exclusive write locks (f_wrlck), and the rules are basically the same: file given byte intervals, multiple processes can have a shared read lock, which allows multiple processes to access the byte area in read mode; File given a byte interval, only one process has an exclusive write lock, that is, only one process-write mode is allowed to access the byte area; the file is given a byte range, and if one or more read locks cannot be added to the byte area, similarly, if there is a write lock, no read and write locks can be added to the byte area.

The FCNTL function is useful for a variety of purposes, and we only discuss the section on record locks, as follows:

int fcntl (int fd, int cmd, struct flock *lock);
Need  #include <fcntl.h>/
*
cmd = f_getlk, test can establish a lock
cmd = f_setlk, set lock
cmd = f_setlkw, blocking set a lock

* *
//posix only defined Fock structure must have the following data members, the implementation can be increased
struct Flock {short
      l_type;    /* Lock type: F_rdlck, F_wrlck, F_unlck * * short
      l_whence;  /* The starting position of the lock: Seek_set, Seek_cur, seek_end respectively, the file header, the current position and file tail * *
      off_t L_start;   /* Lock start offset, relative to l_whence * * *
      off_t L_len;     /* The number of bytes locked, if 0, indicating from the offset to the end of the file * * *
      pid_t l_pid;     /* has occupied the lock PID (only to f_getlk command valid)/
      /*...*/
};
Return value:0 indicates success,-1 indicates failure



F_SETLK (struct flock *) acquire a lock (when l_type are F_rdlck or f_wrlck) or release a lock  (when L_type are f_unlck) on the bytes specified by the l_whence, L_start, and L_len of lock. If a conflicting lock is held by the another process, this call returns-1 and sets to EA

CCEs or Eagain. F_SETLKW (struct flock *) As for f_setlk, but if a conflicting lock are held on the file, the  n wait for this lock to be released. If a signal is caught while waiting, then the ' is ' interrupted and (after the sig‐nal h

Andler has returned) returns immediately (with return value-1 and errno set to Eintr; F_GETLK (struct flock *) on ' input to ', lock describes a lock we would like to Plac  E on the file. If The lock could is placed, fcntl () does not actually place It, but returns F_unlck in the L_type field of lock and leaves the other fields of the structure
              . If one or more incompatible locks would prevent this lock being placed, then FCNTL () returns details about One of these locks in the L_type, L_whence, L_start, and L_len fields of lock and sets L_pid T O is the PID of the process holding that lock.

Be careful to read the explanations above. It should be noted here that the F_GETLK test can be used to establish a lock, and then use F_SETLK or f_setlkw attempt to establish a lock, since the two are not an atomic operation, so there is no guarantee that two times fcntl will not have another process to insert and establish a related lock, So that the initial test condition is invalid. So generally do not want to lock when blocking, will be directly through the call F_setlk, and the return results are tested to determine whether the required lock was successfully established.

1. Record locks can only be used for interprocess synchronization

the rules described above apply only to lock requests made by different processes and do not apply to multiple lock requests made by a single process . That is, if a process has a lock on a file range, and then the process tries to add a lock to the same file range, the new lock will overwrite the old lock.

In cases where the same process is locked, you can continue to lock (the caller will give it after the article)

void *thread_test (void* fd_ptr)
{
	int fd = * (int *) fd_ptr;
	Flock lock;
	Lock_init (&lock, F_wrlck, Seek_set, 0, 0);
	if (Writew_lock (FD) = = 0)
		cout << "Got it!" << Endl; 
	Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl; 
	Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;
}

int main ()
{ 
	char *file_path = ' a.txt ';
	int file_mode = 0664;
	pthread_t pid;
	void *retval; 
	int fd = open (File_path, O_RDWR | O_creat, File_mode); 
	Readw_lock (FD); 

	Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl; 
	Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;

	Pthread_create (&pid, NULL, Thread_test, (void *) &FD);
	Pthread_join (PID, &retval); 
	Unlock (FD); 
	return 0;
}

The output is:

0
0
Got it.
0
0

Description If a process has a lock on a file range, then the process (even another thread under the process) tries to add a lock to the same file range, and the new lock will overwrite the old lock. Therefore, a record lock cannot be used for multithreaded synchronization of the same process.


The following tests are under different processes:

Read the lock in the parent's process to see if the subprocess can continue lock (given by the caller after the article)
int main ()
{  
	char *file_path = "A.txt";
	int file_mode = 0664;
	int retVal;


    	int fd = open (File_path, O_RDWR | O_creat, File_mode);
    	Readw_lock (FD);
	
	if (fork () = = 0)
	{sleep
		(3);
 		cout << "I ' m child." << Endl;
		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;


		Exit (0);
	}
	else
	{	
		cout << "I ' m father." << Endl;
    		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;
	}


	Wait (&retval);
    	Unlock (FD);


    return 0;
}

The results are as follows:

I ' m father.
0
0
I ' m child.
11331
0

Note that the parent process adds a shared read lock, at which point the parent can add any lock, and the child process can only read the lock.

Replace the shared read lock in the parent process with an exclusive write lock:

Write a lock in the parent's process to see if the subprocess can continue lock (given by the caller after the article)

int main ()
{  
	char *file_path = "A.txt";
	int file_mode = 0664;
	int retVal;


    	int fd = open (File_path, O_RDWR | O_creat, File_mode);
    	Readw_lock (FD);
	
	if (fork () = = 0)
	{sleep
		(3);
 		cout << "I ' m child." << Endl;
		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;


		Exit (0);
	}
	else
	{	
		cout << "I ' m father." << Endl;
    		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;
	}


	Wait (&retval);
    	Unlock (FD);


    return 0;
}

Refer to the above test, the result is conceivable:

I ' m father.
0
0
I ' m child.
11393
11393
At this point the parent process adds a write lock, so the child process cannot add any locks.


2. The size of the lock

Here are two concepts: record locking and file locking .

Record locked : For UNIX systems, the term "record" is misused because the UNIX system kernel does not use the concept of file logging at all, and the more appropriate term should be a byte range lock because it locks only one area of the file. The number of bytes in the locked file is represented by granularity . For record locking, the maximum granularity is the entire file.

file Lock : is a special case of the record lock, namely the record lock granularity is the entire file size.

The concept of file locking is because some UNIX systems support locking the entire file, but not the ability to lock the byte range within the file.

3. The implied inheritance and release of the record lock

There are three rules for recording the inheritance and release of Locks, as follows:

(1 The lock is related to both the process and the file : When a process terminates, the record lock it establishes is all released, and when a file descriptor is closed, any lock on the file that the process refers to through the file descriptor is released.

Whether the end of the test process affects the lock that the process adds (given by the calling program after the article)

int main ()
{  
	char *file_path = "A.txt";
	int file_mode = 0664;
	int retVal;

    	int fd = open (File_path, O_RDWR | O_creat, File_mode);
	
	if (fork () = = 0)
	{
    		writew_lock (FD);
 		cout << "I ' m child and I have a writew_lock." << Endl;
		Sleep (3);
		Exit (0);
	}
	else
	{sleep	
		(1);
		cout << "I ' m father." << Endl;
    		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;
		
		Wait (&retval);
		cout << "My child are over." << Endl;
    		Cout<<lock_test (FD, F_wrlck, seek_set, 0, 0) <<endl;
    		Cout<<lock_test (FD, F_rdlck, seek_set, 0, 0) <<endl;
	}

    Unlock (FD);

    return 0;
}

The results are as follows:
I ' m child and I have a writew_lock.
I ' m father.
12149 12149 My child are over
.
0
0
Indicates that the lock it established is invalidated after the child process has ended.

Test the effect of FD on locks #include <iostream> #include <fcntl.h> #include <pthread.h> #include <unistd.h> # Include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/shm.h> using

namespace Std;
	struct share_data{pthread_cond_t cond;
	pthread_mutex_t Mutex;
	pthread_mutexattr_t mutexattr;
pthread_condattr_t condattr;

};
	int main () {char *file_path = ' a.txt ';
	int file_mode = 0664;
	
	int retVal;
	int shmid;

	
	struct Share_data *shm; Shmid = Shmget (ipc_private, sizeof (struct share_data), 0644 |

	Ipc_creat); int fd_1 = open (File_path, O_RDWR |
    	O_creat, File_mode); int fd_2 = open (File_path, O_RDWR |
	
	O_creat, File_mode);
		
		if (fork () = = 0) {SHM = (struct Share_data *) Shmat (shmid, 0, 0); 
		Pthread_mutexattr_init (& (shm->mutexattr));
		Pthread_mutexattr_setpshared (& (Shm->mutexattr), pthread_process_shared);

		Pthread_mutex_init (& (Shm->mutex), & (Shm->mutexattr)); Pthread_condattr_init (& (shm->condattr));
		Pthread_condattr_setpshared (& (Shm->condattr), pthread_process_shared);

		Pthread_cond_init (& (Shm->cond), & (Shm->condattr));
 		Writew_lock (fd_1);
		cout << "I ' m child and I have a writew_lock." << Endl;
		
		Sleep (5);
		Close (fd_1);
		
		Pthread_cond_signal (& (Shm->cond));
		Sleep (3);
	Exit (0);
	
		else {SHM = (struct Share_data *) Shmat (shmid, 0, 0);

		Pthread_mutex_lock (& (Shm->mutex));
		Sleep (1);
    		cout << "I ' m father." << Endl;
    		Cout<<lock_test (Fd_2, F_wrlck, Seek_set, 0, 0) <<endl;
		
		Cout<<lock_test (Fd_2, F_rdlck, Seek_set, 0, 0) <<endl;
		cout << "I ' m waiting." << Endl;

		Pthread_cond_wait (& (Shm->cond), & (Shm->mutex));
    		cout << "My son ' s fd_1 are over." << Endl;
    		Cout<<lock_test (Fd_2, F_wrlck, Seek_set, 0, 0) <<endl; Cout<<lock_test (Fd_2, F_rdlck, Seek_set, 0, 0) <<endl;

    	Wait (&retval);
return 0; }

This example is more complex, except for record locks, as well as mutexes and condition variables. The purpose of mutexes and condition variables is to wake up the main process after the child process close (fd_1), and to see the effect on the parent process after the child process fd_1 the lock and then releases the fd_1.

The results are as follows:

I ' m child and I have a writew_lock.
I ' m father.
17460
17460
I ' m waiting.
My son ' s fd_1 are over.
0
0
proves that when a file descriptor is closed, any lock on the file that the process refers to through the file descriptor is released.

(2) by fork The resulting child process does not inherit the lock set by the parent process. that is, for locks established by the parent process, the child process is considered to be another process. The record lock itself is used to synchronize different processes to operate on the same file area, if the child process inherits the lock of the parent process, then the father-child process can operate on the same file area at the same time, which violates the rules of the record lock, so there is such a rule. The previous code has proved this.


3) Executive exec , the new program can inherit the lock from the original execution program. However, if a file descriptor is set to the CLOSE-ON-EXEC flag, the file descriptor is closed when exec is executed, so the corresponding lock is freed, and it doesn't matter if it inherits.



4.  record lock Read and write precedence
We know that a read-write lock function is a priority for waiting for read mode to occupy a lock thread. A major flaw in this implementation is the occurrence of a write thread starving to death. Then for the record lock, specifically the following 2 aspects of the test: the process has read out the lock, and then write the lock waiting for the extra read lock processing, the process has write lock, then wait for write lock and wait for read out lock priority;

int main ()
{
    int fd = open ("./a.txt", O_RDWR | O_creat, 0664);
    Readw_lock (FD);

    Child  1
    if (fork () = = 0)
    {
        cout<< "Child 1 try-get write lock ..." <<endl;
        Writew_lock (FD);
        cout<< "Child 1 Get write lock ..." <<endl;

        Unlock (FD);
        cout<< "Child 1 release write lock ..." <<endl;
        Exit (0);
    }

    Child 2
    if (fork () = = 0)
    {sleep
        (3);

        cout<< "Child 2 try to get read lock ..." <<endl;
        Readw_lock (FD);
        cout<< "Child 2 get read lock ..." <<endl;

        Unlock (FD);
        cout<< "Child 2 release read lock ..." <<endl;
        Exit (0);
    }

    Sleep (a);
    Unlock (FD);
    
	return 0;
}



The results are as follows:
Child 1 Try-get write lock ...
Child 2 try-get read lock ...
Child 2 Get read lock ...
Child 2 release read lock ...
Child 1 Get write lock
... Child 1 Release Write lock ...

Here you use the F_SETLCKW parameter in the Writew_lock (), Readw_lock (), and unlock () function, which causes the current process to become blocked when the lock is not obtained, waiting until the lock can be obtained (return 0) or a signal (return-1) is received. It is worth noting that Subprocess 1 obtains a write lock after the child process 2 releases the lock and it takes a long time, apparently after the parent process unlock (FD). It can be seen that, in the case of a write process waiting, the system will satisfy the read process (that is) for the request to read the process. This could also lead to a situation where the write process starved to death.

int main ()
{
    int fd = open ("./a.txt", O_RDWR | O_creat, 0664);
    Writew_lock (FD);
	int retVal;

    Child  1
    if (fork () = = 0)
    {
        cout<< "Child 1 try-get write lock ..." <<endl;
        Writew_lock (FD);
        cout<< "Child 1 Get write lock ..." <<endl;

        Unlock (FD);
        cout<< "Child 1 release write lock ..." <<endl;

        Exit (0);
    }

    Child 2
    if (fork () = = 0)
    {sleep
        (3);
        cout<< "Child 2 try to get read lock ..." <<endl;
        Readw_lock (FD);
        cout<< "Child 2 get read lock ..." <<endl;

        Unlock (FD);
        cout<< "Child 2 release read lock ..." <<endl;

        Exit (0);
    }

    Sleep (a);
    Unlock (FD);

    return 0;
}

My test results are as follows:
Child 1 Try-get write lock ...
Child 2 try-get read lock ...
Child 2 Get read lock ...
Child 2 release read lock ...
Child 1 Get write lock
... Child 1 Release Write lock ...
The results show that the priority of reading locks is high. Different from the results FIFO of the reference blog ... Let's do this first.
The last thing to say is that the file Descriptor FD Fork () is in the child process following the parent process, although it points to the same file, but it is already two different FD. See the following example:
int main ()
{
    int fd = open ("./a.txt", O_RDWR | O_creat, 0664);
    Writew_lock (FD);
    
	Child
    if (fork () = = 0)
    {
        cout<< "child try-get write lock ..." <<endl;
        Writew_lock (FD);
        cout<< "Child get Write lock ..." <<endl;

        Unlock (FD);
        cout<< "Child release write lock ..." <<endl;

        Exit (0);
    }
	
	Sleep (5);
	Close (FD);

	Sleep (5);
    return 0;
}
Results:
Child try-Get write lock ...
Child Get write lock ...
Child Release Write lock ...
Among them 1, 22 sentences, 3 sentences and the end has a longer time to wait. Indicates the reason for the sleep () function. When the FD in the parent process is reclaimed, the child process gets the write lock.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.