zoukankan      html  css  js  c++  java
  • linux c编程:互斥锁

    们常说互斥锁保护临界区,实际上是说保护临界区中被多个线程或进程共享的数据。互斥锁保证任何时刻只有一个线程在执行其中的代码。

    互斥锁具有以下特点:


    ·原子性:把一个互斥锁定义为一个原子操作,这意味着操作系统保证了如果一个线程锁定了互斥锁,则没有其他线程可以在同一时间成功锁定这个互斥量。


    ·唯一性:如果一个线程锁定一个互斥量,在它接触锁定之前,没有其他线程可以锁定这个互斥量。


    ·非繁忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将被挂起(不占用CPU资源),直到第一个线程解锁,第二个线程则被唤醒并继续执行,同时锁定这个互斥量


    创建互斥锁需要用到下面几个函数和变量:

    在使用互斥锁之前,需要先创建一个互斥锁的对象。 互斥锁的类型是 pthread_mutex_t ,所以定义一个变量就是创建了一个互斥锁。

    pthread_mutex_t mtx;
    这就定义了一个互斥锁。但是如果想使用这个互斥锁还是不行的,我们还需要对这个互斥锁进行初始化, 使用函数 pthread_mutex_init() 对互斥锁进行初始化操作。
    pthread_mutex_init(&mtx, NULL);

    除了使用 pthread_mutex_init() 初始化一个互斥锁,我们还可以使用下面的方式定义一个互斥锁:

    pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

    在头文件 /usr/include/pthread.h 中,对 PTHREAD_MUTEX_INITIALIZER 的声明如下

    # define PTHREAD_MUTEX_INITIALIZER 
       { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
    为什么可以这样初始化呢,因为互斥锁的类型 pthread_mutex_t 是一个联合体, 其声明在文件 /usr/include/bits/pthreadtypes.h 中,代码如下:
    /* 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;
    #if __WORDSIZE == 64
            unsigned int __nusers;
    #endif
            /* KIND must stay at this position in the structure to maintain
               binary compatibility.  */
            int __kind;
    #if __WORDSIZE == 64
            int __spins;
            __pthread_list_t __list;
    # define __PTHREAD_MUTEX_HAVE_PREV  1
    #else
            unsigned int __nusers;
            __extension__ union
            {
    int __spins;
                __pthread_slist_t __list;
            };
    #endif
        } __data;
    char __size[__SIZEOF_PTHREAD_MUTEX_T];
    long int __align;
    } pthread_mutex_t;
    
    获取互斥锁:

    接下来是如何使用互斥锁进行互斥操作。在进行互斥操作的时候, 应该先"拿到锁"再执行需要互斥的操作,否则可能会导致多个线程都需要访问的数据结果不一致。 例如在一个线程在试图修改一个变量的时候,另一个线程也试图去修改这个变量, 那就很可能让后修改的这个线程把前面线程所做的修改覆盖了。

    下面是获取锁的操作:

    阻塞调用

    pthread_mutex_lock(&mtx);

    这个操作是阻塞调用的,也就是说,如果这个锁此时正在被其它线程占用, 那么 pthread_mutex_lock() 调用会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁之后才会返回。

    非阻塞调用

    如果不想阻塞,而是想尝试获取一下,如果锁被占用咱就不用,如果没被占用那就用, 这该怎么实现呢?可以使用 pthread_mutex_trylock() 函数。 这个函数和 pthread_mutex_lock() 用法一样,只不过当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。

    int err = pthread_mutex_trylock(&mtx);
    if(0 != err) {
    if(EBUSY == err) {
    //The mutex could not be acquired because it was already locked.
        }
    }

    超时调用

    如果不想不断的调用 pthread_mutex_trylock() 来测试互斥锁是否可用, 而是想阻塞调用,但是增加一个超时时间呢,那么可以使用 pthread_mutex_timedlock() 来解决, 其调用方式如下:

    struct timespec abs_timeout;
    abs_timeout.tv_sec = time(NULL) + 1;
    abs_timeout.tv_nsec = 0;
    
    int err = pthread_mutex_timedlock(&mtx, &abs_timeout);
    if(0 != err) {
    if(ETIMEDOUT == err) {
    //The mutex could not be locked before the specified timeout expired.
        }
    }

    上面代码的意思是,阻塞等待线程锁,但是只等1秒钟,一秒钟后如果还没拿到锁的话, 那就返回,并返回一个错误代码 ETIMEDOUT,意思是超时了。

    其中 timespec 定义在头文件 time.h 中,其定义如下

    struct timespec
    {
        __time_t tv_sec;        /* Seconds.  */
        long int tv_nsec;       /* Nanoseconds.  */
    };

    释放互斥锁

    用完了互斥锁,一定要记得释放,不然下一个想要获得这个锁的线程, 就只能去等着了,如果那个线程很不幸的使用了阻塞等待,那就悲催了。

    释放互斥锁比较简单,使用 pthread_mutex_unlock() 即可:

    pthread_mutex_unlock(&mtx);

    同步中有一个称为生产者-消费者(producer-consumer)的经典问题。一个或多个生产者(线程或进程)产生一个个数据条目,这些条目由一个或多个消费者(线程或进程)处理。数据条目在生产者和消费者之间通过某种类型的IPC传递。

    这里以生产者和消费者为例,模型如下:

    根据这个模型可以构造相关的代码:

    #include <pthread.h>

    #include <stdio.h>


    #define MAXNITEMS 1000000

    #define MAXNTHREADS 5



    int nitems;



    struct

    {

    pthread_mutex_t mutex;

    int buff[MAXNITEMS];

    int nput;

    int nval;

    }shared={PTHREAD_MUTEX_INITIALIZER};


    void *produce(void *arg)

    {

    for(;;)

    {

    pthread_mutex_lock(&shared.mutex);

    if(shared.nput >= nitems)

    {

    pthread_mutex_unlock(&shared.mutex);

    return NULL;

    }

    shared.buff[shared.nput]=shared.nval;

    shared.nput++;

    shared.nval++;

    pthread_mutex_unlock(&shared.mutex);

    *((int *)arg)+=1;

    }

    }




    void *consumer(void *arg)

    {

    int i;

    for(i=0;i<=nitems;i++)

    {

    if(shared.buff[i] != i)

    printf("buff[%d]=%d ",i,shared.buff[i]);

    }

    return NULL;

    }



    int produce_consumer()

    {

    int i,nthreads,count[MAXNTHREADS];

    pthread_t tid_produce[MAXNTHREADS],tid_consume;

    nitems=MAXNITEMS;

    nthreads=MAXNTHREADS;

    pthread_setconcurrency(nthreads);

    for(i=0;i<nthreads;i++)

    {

    count[i]=0;

    pthread_create(&tid_produce[i],NULL,produce,&count[i]);

    }

    for(i=0;i<nthreads;i++)

    {

    pthread_join(tid_produce[i],NULL);

    printf("count[%d]=%d ",i,count[i]);

    }

    pthread_create(&tid_consume,NULL,consumer,NULL);

    pthread_join(tid_consume,NULL);

    return 1;

    }


    1 首先我们创建生产者线程,每个线程执行produce。在tid_produce数组中保存每个线程的线程ID。传递给每个生产者线程的参数是指向count数组中某个元素的指针。首先把该计数器初始化为0.然后每个线程在每次往缓冲区中存放一个条目时给这个计数器加1。当一切生成完毕时,我们输出这个计数器数组各元素的值,以查看每个生产者线程分别存放了多少条目

    2 等待所有生产者线程终止,同时输出每个线程的计数器值,此后才启动单个消费者线程


    程序编译报错:

    pthread.c:(.text+0x85):对‘pthread_create’未定义的引用。


    由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a

    codeblocks编译器中加入libpthread.so的共享库

    或者在用GCC编译程序的时候加上-lpthread参数:

    程序运行结果如下

  • 相关阅读:
    第四次博客作业-结对项目
    软件工程第三次作业——关于软件质量保障初探
    java 自动生成四则运算式
    《构建之法》第一章学习笔记
    给大家分享一个小程序—2048
    Java异常处理认识
    轻松定位CPU飙高问题
    接手​「烂摊子」,管理者怎么办?
    业务中台建设篇
    互联网巨头“大中台,小前台”战略
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/10294975.html
Copyright © 2011-2022 走看看