LINUX锁之互斥锁(C++)

互斥量也可以叫互斥锁。

内核通过时间分片的方式来管理线程,使得多个线程好像在并发执行。

为什么多线程比多进程更有优势?

因为属于同一进程的多个线程共享同一内存空间,利用同步操作,不同线程可以通过读取共享内存中的变量来进行通信。但是进程由于拥有独立的虚拟内存空间,所以要进行通信必须通过IPC的方式,这个方式有两个问题:

1.强加了某种形式的额外内核开销,从而降低了系统性能;

2.IPC的使用会导致程序变得更加复杂。

不加锁的多线程由于系统分时模拟多任务,会使得程序运行不会按照想要的结果输出。以下面一个程序为例。子线程对全局变量进行20次加一操作,主线程同时也进行加1操作,结果输出却为37.

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
 void *thread_function(void *arg) {
  int i,j;
  for ( i=0; i<20; i++) {
    j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
    sleep(1);
    myglobal=j;
  }
  return NULL;
}
int main(void) {
  pthread_t mythread;
  int i;
  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    printf("error creating thread.");
    abort();
  }
  for ( i=0; i<20; i++) {
    myglobal=myglobal+1;
    printf("o");
    fflush(stdout);
    sleep(1);
  }
  if ( pthread_join ( mythread, NULL ) ) {
    printf("error joining thread.");
    abort();
  }
  printf("\nmyglobal equals %d\n",myglobal);
  exit(0);
}

 为什么最终结果不等于40,分析如下:

在子线程中将全局变量赋值给j,对j进行+1操作,此过程中主线程可能已经完成了对全局变量的+1操作,当再把j赋值给全局变量时,主线程的操作被覆盖,因此最终不会是40.

其实对全局变量的+1操作完全可以不借助j,但是实际情况是,对于全局变量的操作,子线程有时会占用很长时间,主线程在此期间极有可能对其进行更改,这种更改在子线程完成之后会被子线程覆盖,这并不是我们想要的,因此,必须限制同一时刻,只允许一个线程访问公共资源。

对于全局变量的访问,为了保证两个线程能够单独完成对该变量的使用,我们可以使用互斥量。

下面的一个例子是对上面程序的改进,具体地,在main()函数中创建子线程,子线程完成对全局变量的20次+1操作,子线程在访问全局变量之前对其加锁,从而保证同一时刻只有一个线程访问全局变量;主线程也对全局变量进行加锁操作,同样,访问全局变量之前对其加锁操作。

互斥量的定义和初始化方式如下:

静态初始化方法
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
动态初始化
当互斥量是通过malloc()分配时,必须通过动态方法初始化
pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
int err=pthread_mutex_init(mutex,NULL);
if(err!=0){
    fprintf(stderr,"can't init muext%s\n",strerror(err));
    exit(1);
}

互斥量非0即1,初始化时,资源为1,表示有一个资源可用,对资源加锁成功之后,mutex-1,mutex=0,表示没有资源可用,解锁时,释放资源,mutex+1,mutex=1,表示有一个资源可用。
int pthread_mutex_init(pthread_mutex_t *thread,const pthread_mutexattr_t *attr)
初始化成功时,返回0;

对互斥量进行加锁和解锁的方式如下:

pthread_mutex_lock(&mutex);//函数调用成功时返回零,失败时返回非零的错误代码。
pthread_mutex_unlock(&mutex);//对未加锁的锁变量解锁会导致该函数返回一个非零的 EPERM 

还有另一种加锁的方式不同于pthread_mutex_lock():
pthread_mutex_trylock(pthread_mutex_t *muetx);
如果锁变量mutex没有被其他线程加锁,则可以对mutex加锁,加锁成功返回0;如果mutex已经被锁定,则此线程不会被阻塞,同时返回一个非零的值,然后线程继续做其他事情。
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg) {
    int i,j;
    for ( i=0; i<20; i++) {
        pthread_mutex_lock(&mutex);
        j=myglobal;
        j=j+1;
        printf(".");
        fflush(stdout);
        sleep(1);
        myglobal=j;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(void) {
    pthread_t mythread;
    int i;
    if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
        printf("error creating thread.");
        abort();
    }
    for ( i=0; i<20; i++) {
        pthread_mutex_lock(&mutex);
        myglobal=myglobal+1;
        pthread_mutex_unlock(&mutex);
        printf("o");
        fflush(stdout);
        sleep(1);
    }
    if ( pthread_join ( mythread, NULL ) ) {
        printf("error joining thread.");
        abort();
    }
    printf("\nmyglobal equals %d\n",myglobal);
    exit(0);
}

输出结果如下:

o.o...................oooooooooooooooooo
myglobal equals 40

当某一线程调用pthread_mutex_lock(&mutex)时,如果发现该互斥量不被任何线程占有,则对mutex加锁,如果发现mutex已经被加锁,则该线程被阻塞;如果有多个线程调用pthread_mutex_lock(&mutex),同时发现mutex变量已经被加锁,那么这些线程均会被阻塞,至于mutex被解锁后,该调用哪个线程对mutex进行加锁,系统有自己的调度算法,最普通的可能就是先到先得。

可以看到互斥量的使用,可以使得线程能够串行地存取共享数据。

互斥量并不是万能的,如果某一线程等待共享资源的某一条件出现,反复的锁定与解锁是对内核资源的一种浪费。我们可以使用条件变量,当条件不满足时,线程进入睡眠状态,因此不会浪费内核资源,当条件满足时,唤醒该线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值