Linux c++ 多线程编程基础——互斥锁

1. 前言

    本来是想整理一份多线程编程的博客,才发现C++的多线程比Java还要坑。涉及的范围实在有点广,所以之后分开来慢慢讲解,先说这个互斥锁。

    首先是互斥,这是什么呢?说起来就又是一张的内容,详细自己去了解一下,参考书籍《操作系统——精髓与设计原理(第七版)》第五章,我这里将要说一下就好了。

    互斥就是有一个进程使用了一个临界区的资源,另一个进程就无法使用。举个例子吧,在交通道路上,红灯停绿灯行,当你是红灯的时候,你就需要停下来等待,相反的如果你是绿灯,你就可以通信,这就好比红灯和绿灯是一个信号量,如果有一条道上是绿灯,那么这条道上的车都可以通过,而没有得到绿灯的道就只能等待,这就是互斥。


2. 互斥锁pthread_mutex_t

    (1)创建互斥锁

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    pthread_mutexattr_t mut_at;
    pthread_mutexattr_init(&mut_at);

    pthread_mutex_t mut;
    mut = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_init(&mut,&mut_at);
}

创建互斥锁有两种方法,一种就是直接使用PTHREAD_MUTEX_INITIALIZER直接初始化,PTHREAD_MUTEX_INITIALIZER在底层的实现如下:

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, __PTHREAD_SPINS, { 0, 0 } } }

__PTHREAD_SPINS的底层实现如下:

# define __PTHREAD_SPINS             0, 0

说白了,使用这个就是把锁里面的值全部赋值成0。

第二种,调用pthread_mutex_init,其函数原型如下:

/* Initialize a mutex.  */
extern int pthread_mutex_init (pthread_mutex_t *__mutex,
			       const pthread_mutexattr_t *__mutexattr)
     __THROW __nonnull ((1));
使用一个pthread_mutexattr_t的指针来初始化pthread_mutex_t

看一下两个结构体的实现就知道,其实这两个基本是一样的,只是pthread_mutex_t比pthread_mutexattr_t多了一个结构体,内部定义如下:

/* Data structures for mutex handling.  The structure of the attribute
   type is not exposed on purpose.  */
typedef union
{
  struct __pthread_mutex_s
  {
    int __lock;
    unsigned int __count;
    int __owner;
#ifdef __x86_64__
    unsigned int __nusers;
#endif
    /* KIND must stay at this position in the structure to maintain
       binary compatibility.  */
    int __kind;
#ifdef __x86_64__
    short __spins;
    short __elision;
    __pthread_list_t __list;
# define __PTHREAD_MUTEX_HAVE_PREV	1
/* Mutex __spins initializer used by PTHREAD_MUTEX_INITIALIZER.  */
# define __PTHREAD_SPINS             0, 0
#else
    unsigned int __nusers;
    __extension__ union
    {
      struct
      {
	short __espins;
	short __elision;
# define __spins __elision_data.__espins
# define __elision __elision_data.__elision
# define __PTHREAD_SPINS         { 0, 0 }
      } __elision_data;
      __pthread_slist_t __list;
    };
#endif
  } __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;

这个结构体内部定义了一些比如使用线程的对象标识符,对象等待的队列,锁的属性等数据信息。

(其实我也很纠结这些东西到底是什么东西,想想看一个锁到底需要什么,为什么需要这么多的东西。这个等我深入了解在进行解释)

    (2) 锁的作用范围:

互斥锁主要的作用范围有两个:

    一个就是用于同一个进程内部线程的同步,对应的为PTHREAD_PROCESS_PRIVATE

    另一个是使用在不同进程内部线程的同步,对应的为PTHREAD_PROCESS_SHAPE

如何设置这个变量的值呢?可以参看以下的代码:

int main()
{
    pthread_mutexattr_t mut_at;
    pthread_mutex_t mut;
    pthread_mutexattr_init(&mut_at);
    // 将mut_at的作用范围变量设置为PTHREAD_PROCESS_SHARED
    pthread_mutexattr_setpshared(&mut_at,PTHREAD_PROCESS_SHARED);
    // 将mut_at的作用范围变量设置为PTHREAD_PROCESS_PRIVATE
    pthread_mutexattr_setpshared(&mut_at,PTHREAD_PROCESS_PRIVATE);
    pthread_mutex_init(&mut,&mut_at);
}

获取这个属性只需要把set改成get,下面是其对应的函数原型:

/* Get the process-shared flag of the mutex attribute ATTR.  */
extern int pthread_mutexattr_getpshared (const pthread_mutexattr_t *
					 __restrict __attr,
					 int *__restrict __pshared)
     __THROW __nonnull ((1, 2));

/* Set the process-shared flag of the mutex attribute ATTR.  */
extern int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,
					 int __pshared)
     __THROW __nonnull ((1));

不知道怎么做的可以看一下原型里面的参数。

    (3) 属性:

PTHREAD_MUTEX_TIMED_NP:该属性为缺省属性,就是默认值。当一个线程加锁以后,其余请求锁的线程形成一个等待队列,并在解锁后按优先级获得锁。

PTHREAD_MUTEX_RECURSIVE_NP:嵌套所,该属性允许同一个线程对同一个锁多次获取,并通过unlock多次解锁。如果不同线程请求,则需要在该线程解锁之后竞争。

PTHREAD_MUTEX_ERRORCHECK_NP:检错锁。功能同第一个,只是这个锁在同一个线程请求同一个锁的时候会返回EDEADLK,其他的同第一个,这个只是保证没有任何一个线程获取同一个锁两次以上。

PTHREAD_MUTEX_ADAPTIVE_NP:适应锁。

以上4个为属性,介绍来是怎么设置这些属性呢?这时候就需要以下函数的支持:

/* Return in *KIND the mutex kind attribute in *ATTR.  */
extern int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict
				      __attr, int *__restrict __kind)
     __THROW __nonnull ((1, 2));

/* Set the mutex kind attribute in *ATTR to KIND (either PTHREAD_MUTEX_NORMAL,
   PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_ERRORCHECK, or
   PTHREAD_MUTEX_DEFAULT).  */
extern int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind)
     __THROW __nonnull ((1));

一个为获取锁的属性,一个是设置锁的属性。具体使用如下:

pthread_mutexattr_settype(&mut_at,PTHREAD_MUTEX_TIMED_NP);
pthread_mutex_init(&mut,&mut_at);

    (4) 锁的操作:

/* Try locking a mutex.  */
extern int pthread_mutex_trylock (pthread_mutex_t *__mutex)
     __THROWNL __nonnull ((1));

/* Lock a mutex.  */
extern int pthread_mutex_lock (pthread_mutex_t *__mutex)
     __THROWNL __nonnull ((1));

/* Unlock a mutex.  */
extern int pthread_mutex_unlock (pthread_mutex_t *__mutex)
     __THROWNL __nonnull ((1));

这四个函数做一下说明:

pthread_mutex_lock:加锁。如果锁已经被占有,则该线程加入一个队列中。

pthread_mutex_trylock:尝试加锁,如果锁已被占有,则线程不加入队列,而是返回错误。

pthread_mutex_unlock:释放锁。

    (5) 具体示例:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

int tf[5];

void* print(void* i)
{
    pthread_mutex_lock(&mut);
    for(int j=0;j<5;j++)
        cout << i << " " << j << endl;
    pthread_mutex_unlock(&mut);
}

int main()
{
    pthread_t td[5];
    for(int i=0;i<5;i++)
        tf[i] = i;
    for(int i=0;i<5;i++)
        pthread_create(&td[i],NULL,print,(void *)&tf[i]);
    for(int i=0;i<5;i++)
        pthread_join(td[i],NULL);
    pthread_mutex_destroy(&mut);
}

这样每一个线程都需要运行到结束才可以释放锁,其他线程才可以进行访问。运行结果如下:

0x602194 0
0x602194 1
0x602194 2
0x602194 3
0x602194 4
0x602190 0
0x602190 1
0x602190 2
0x602190 3
0x602190 4
0x6021a0 0
0x6021a0 1
0x6021a0 2
0x6021a0 3
0x6021a0 4
0x60219c 0
0x60219c 1
0x60219c 2
0x60219c 3
0x60219c 4
0x602198 0
0x602198 1
0x602198 2
0x602198 3
0x602198 4


现在讲这个互斥锁,有错误的话请多多指教。


参考链接:

https://blog.csdn.net/zmxiangde_88/article/details/7998458



你可能感兴趣的:(C++,多线程编程,linux)