Linux C Programming: Synchronization Properties

Source: Internet
Author: User
Tags mutex posix

Just as a thread has properties, a thread's synchronization objects (such as mutexes, read-write locks, condition variables, spin locks, and barriers) also have properties
1. Mutex attribute
Using Pthread_mutexattr_init to initialize the pthread_mutexattr_t structure, the structure is recycled with the Pthread_mutexattr_destroy.

#include <pthread.h>

int Pthread_mutexattr_init (pthread_mutexattr_t *attr);

int Pthread_mutexattr_destroy (pthread_mutexattr_t *attr);

Return value: Returns 0 if successful, otherwise returns the error number

The Pthread_mutexattr_init function initializes the pthread_mutexattr_t structure with the default mutex property. Notable two properties are process share properties and type properties. POSIX.1, the process sharing property is optional and can be used to determine whether the platform supports process sharing by checking whether the _posix_thread_process_shared symbol is defined in the system, or it can be used at run time to _sc_thread_process The _shared parameter is passed to the SYSCONF function for inspection. Although this option is not required by the POSIX-compliant operating system, the single UNIX specification requires operating systems that follow the XSI standard to support this option.
In a process, multiple threads can access the same synchronization object, which is the default behavior. In this case, the process share mutex property needs to be set to Pthread_process_private.
There is a mechanism that allows multiple processes that are independent of each other to map the same area of memory to their respective separate address spaces. Just as multiple threads access shared data, multiple processes often require synchronization to access shared data. If the process share mutex property is set to Pthread_process_shared, the amount of mutexes allocated from the memory regions shared by multiple processes can be used for synchronization of those processes.
You can use the Pthread_mutexattr_getpshared function to query the pthread_mutexattr_t structure to get its process sharing properties, and you can modify the process sharing properties with the Pthread_mutexattr_setpshared function.

#include <pthread.h>

int pthread_mutexattr_getpshared (const pthread_mutexattr_t *restrict attr, int *restrict pshared);

int pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int pshared);

Return value: Returns 0 if successful, otherwise returns the error number

When the process shared mutex property is set to Pthread_process_private, the Pthread line libraries is allowed to provide a more efficient mutex implementation, which is the default condition in multithreaded applications. The Pthread line libraries can limit the cost of a large mutex implementation in cases where multiple processes share multiple mutexes.
The type mutex property controls the attribute of the mutex. There are four types of posix.1 defined. The Pthread_mutex_normal type is a standard mutex type and does not do any special error checking or deadlock detection. The Pthread_mutex_errorcheck mutex type provides error checking.
The Pthread_mutex_recursive mutex type allows the same thread to lock the mutex multiple times before the mutex is unlocked. Maintains a count of locks with a recursive mutex. Locks are not released when the number of times to unlock and the number of locking are different. So if a recursive mutex is locked two times and then unlocked once, the mutex is still locked and cannot be released until it is unlocked again.
Finally, the Pthread_mutex_default type can be used to request the default semantics. The operating system can freely map this type to other types when it is implemented. For example, in Linux, this type is mapped to a common mutex type.
The four types of behavior are shown in the following table. The column "unlock when not in use" refers to the case where a thread unlocks a mutex that is Cheng by another line; the "Unlock when unlocked" column refers to what happens when a thread unlocks a mutex that has been unlocked, which is usually caused by a coding error.

You can use the Pthread_mutexattr_gettype function to get the mutex Type property, and the Pthread_mutexattr_settype function to modify the mutex Type property.

#include <pthread.h>

int Pthread_mutexattr_gettype (const pthread_mutexattr_t * Restrict attr, int *restrict type);

int Pthread_mutexattr_settype (pthread_mutexattr_t *attr, int type);

The return value of both is: returns 0 if successful, otherwise returns the error number

The mutex can be used to protect the condition associated with the condition variable. Before blocking a thread, the pthread_cond_wait and pthread_cond_timedwait functions release the mutex associated with the condition, which allows other threads to obtain the mutex, change the condition, release the mutex, and send a signal to the condition variable. Since it is necessary to have a mutex when changing the condition, it is not a good idea to use recursive mutexes. If the recursive mutex is locked more than once and then used in the call to the Pthread_cond_wait function, the condition will never be satisfied, because the pthread_cond_wait's unlock operation does not release the mutex.
Recursive mutexes are useful if you need to put an existing single-threaded interface into a multithreaded environment, but you cannot modify the function interface due to program compatibility limitations. However, since the use of recursive locks requires a certain skill, it should only be used in the absence of other feasible alternatives.
When using a recursive lock:

Main ()

{

FUNC1 (x);

......

FUNC2 (x);

}

Func1 ()

{

Pthread_mutex_lock (X->lock);

......

FUNC2 (x);

......

Pthread_mutex_unlock (X->lock);

}

Func2 ()

{

Pthread_mutex_lock (X->lock);

......

Pthread_mutex_unlock (X->lock);

}

The code above explains how recursive locks appear to solve concurrency problems. Assuming that func1 and FUNC2 are existing functions in the library, their interfaces cannot be changed because there are applications that call both interfaces, and the application cannot be altered.
In order to maintain the same interface with the original, you can embed the mutex into the data structure, the data structure of the address (x) as a parameter passed in. This scenario is only feasible if the allocation function is provided for the data structure, so the application does not know the size of the data structure (it is assumed that the size of the data structure must be enlarged after the mutex is added).
This approach is also possible if you have reserved enough fields to be filled in when you initially define the data structure, allowing you to replace some of the fill fields with mutexes. However, most programmers are not good at predicting the future, so this is not a universally feasible experience.
If both the FUNC1 and FUNC2 functions must manipulate the structure, and there may be multiple threads accessing the data structure at the same time, then func1 and Func2 must lock the mutex before manipulating the data. When func1 must call Func2, a deadlock occurs if the mutex is not a recursive type. If you can release the mutex before calling Func2, and regain the mutex after Func2 returns, you can avoid using recursive mutexes, but this also gives the opportunity to other threads that other threads may gain control of the mutex during func1 execution and modify the data structure.
Avoid the use of recursive locks

Main ()

{

FUNC1 (x);

......

FUNC2 (x);

}

Func1 ()

{

Pthread_mutex_lock (X->lock);

......

func2_locked (x);

......

Pthread_mutex_unlock (X->lock);

}

Func2 ()

{

Pthread_mutex_lock (X->lock);

func2_locked (x);

Pthread_mutex_unlock (X->lock);

}

The code above shows another alternative to using recursive mutexes in this case. By providing a private version of the FUNC2 function (called the func2_locked function), you can keep the func1 and Func2 function interfaces intact, and avoid using recursive mutexes. To invoke the Func2_locked function, you must occupy the mutex that is embedded in the data structure, and the address of the data structure is passed in as a parameter. The func2_locked function Body contains a copy of the Func2 (original, Func2 without a mutex), and Func2 is now only used to obtain the mutex, call func2_locked, and finally release the mutex.
If you do not necessarily want to keep the library function interface intact, you can add another parameter to each function to indicate whether the structure is locked by the caller. However, if possible, it is usually better to keep the interface intact, which avoids the undesirable effects of the artifacts that are involved in the implementation process on the original interface.
Providing a lock-and-lock version of a function, such a strategy is usually feasible in simple cases. In more complex cases, such as libraries that need to call functions outside the library, and possibly callback functions in the library again, you need to rely on recursive locks.
The following explains another case where it is necessary to use recursive mutexes. Here, there is a "timeout" function, which allows another function to be scheduled to run at some time in the future. Assuming that the thread is not a very expensive resource, you can create a thread for each pending timeout function. The thread waits until the time is up and calls the requested function when the time is up.

#include "apue.h"

#include <pthread.h>

#include <time.h>

#include <sys/time.h>

extern int Makethread (void * (*) (void *), void *);

struct to_info{

void (*TO_FN) (void *); /* Function */

void *to_arg; /* argument */

struct Timespec to_wait; /* Time to wait */

};

#define SECTONSEC 1000000000/* seconds to nanoseconds */

#define USECTONSEC/* microseconds to nanoseconds */

void *

Timeout_helper (void *arg)

{

struct To_info *tip;

tip = (struct to_info *) arg;

Nanosleep (&tip->to_wait, NULL);

(*TIP->TO_FN) (Tip->to_arg);

return (0);

}

void

Timeout (const struct TIMESPEC *when, void (*func) (void *), void *arg)

{

struct TIMESPEC now;

struct Timeval TV;

struct To_info *tip;

int err;

Gettimeofday (&TV, NULL);

Now.tv_sec = tv.tv_sec;

now.tv_nsec = tv.tv_usec * USECTONSEC;

if ((When->tv_sec > now.tv_sec) | |

(when->tv_sec = = now.tv_sec && when->tv_nsec > Now.tv_nsec))

{

tip = malloc (sizeof (struct to_info));

if (tip! = NULL)

{

TIP->TO_FN = func;

Tip->to_arg = arg;

Tip->to_wait.tv_sec = when->tv_sec-now.tv_sec;

if (when->tv_nsec >= now.tv_nsec)

{

Tip->to_wait.tv_nsec = when->tv_nsec-now.tv_nsec;

}

Else

{

tip->to_wait.tv_sec--;

Tip->to_wait.tv_nsec = sectonsec-now.tv_nsec + when->tv_nsec;

}

Err = Makethread (Timeout_helper, (void *) tip);

if (err = = 0)

Return

}

}

/*

* We Get here if (a) when <= now, or (b) malloc fails, or

* (c) We can ' t make a thread, so we just call the function now.

*/

(*func) (ARG);

}

pthread_mutexattr_t attr;

pthread_mutex_t Mutex;

void

Retry (void *arg)

{

Pthread_mutex_lock (&mutex);

/* Perform retry steps ... */

Pthread_mutex_unlock (&mutex);

}

Int

Main (void)

{

int err, condition, ARG;

struct TIMESPEC when;

if (err = = Pthread_mutexattr_init (&attr))! = 0)

Err_exit (Err, "Pthread_mutexattr_init failed");

if (err = = Pthread_mutexattr_settype (&attr,

pthread_mutex_recursive))! = 0)

Err_exit (Err, "can ' t set recursive type");

if (err = = Pthread_mutex_init (&mutex, &attr))! = 0)

Err_exit (Err, "can ' t create recursive mutex");

/* ... */

Pthread_mutex_lock (&mutex);

/* ... */

if (condition)

{

/* Calculate target Time "when" */

Timeout (&when, retry, (void *) arg);

}

/* ... */

Pthread_mutex_unlock (&mutex);

/* ... */

Exit (0);

}

The problem arises if the thread cannot be created, or if the time the function is scheduled to run has elapsed. In this case, you want to invoke the function that was requested to run from the current environment, because the lock that the function wants to acquire is the same as the lock that is now in possession, unless the lock is recursive, otherwise a deadlock occurs.
Here, the Makethread function in Listing 12-1 is used to create the thread in a detached state. You want the function to run at some time in the future, and you don't want to wait for the thread to end.
You can call the sleep wait time-out to arrive, but it provides a time granularity of seconds, and if you want to wait for a time that is not an integer second, you need to use the Nanosleep (2) function, which provides similar functionality.
The caller of timeout needs to have a mutex to check the condition and arrange the retry function as an atomic operation. The retry function attempts to lock the same mutex, so unless the mutex is recursive, if the timeout function calls retry directly, it causes a deadlock.
Read-Write Lock properties
Read-write locks are similar to mutexes and have properties. pthread_rwlockattr_t structure is initialized with Pthread_rwlockattr_init, and the structure is recycled with Pthread_rwlockattr_destroy.

#include <pthread.h>

int Pthread_rwlockattr_init (pthread_rwlockattr_t *attr);

int Pthread_rwlockattr_destroy (pthread_rwlockattr_t *attr);

The return value of both is: returns 0 if successful, otherwise returns the error number

The only property supported by read-write locks is the process share property, which is the same as the mutex process share property. Just like the mutex process share property, a pair of functions is used to read and set the process share properties of the read-write lock.

#include <pthread.h>

int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * Restrict attr, int *restrict pshared);

int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *attr, int pshared);

The return value of both is: returns 0 if successful, otherwise returns the error number

Condition Variable Properties
Condition variables also have properties. Similar to mutex and read-write locks, there is a pair of functions for initializing and reclaiming conditional variable properties.

#include <pthread.h>

int Pthread_condattr_init (pthread_condattr_t *attr);

int Pthread_condattr_destroy (pthread_condattr_t *attr);

The return value of both is: returns 0 if successful, otherwise returns the error number

Like other synchronization primitives, conditional variables support process sharing properties.

#include <pthread.h>

int pthread_condattr_getpshared (const pthread_condattr_t *restrict attr, int *restrict pshared);

int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared);

The return value of both is: returns 0 if successful, otherwise returns the error number










Linux C Programming: Synchronization Properties

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.