互斥锁、条件变量

一、互斥锁

1. 函数原型:

pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);  
pthread_mutex_destroy(pthread_mutex_t *mutex); 

分析:

  • pthread_mutex_t 类型,其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数看待。
  • pthread_mutex_t  mutex:变量mutex只有两种取值0、1;

函数一参数1:传出参数,调用时应传&mutex

  • restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。

函数一参数2:互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).

  • 静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 动态初始化:局部变量应采用动态初始化, pthread_mutex_init(&mutex, NULL);

 

pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock(pthread_mutex_t *mutex);                                        

分析:

  • 函数1:没有被上锁,当前线程会将这把锁锁上;被锁上了,当前线程阻塞,锁被打开之后,线程解除阻塞(加锁。可理解为将mutex--(或-1))。
  • 函数2:同时将阻塞在该锁上的所有线程全部唤醒解锁(可理解为将mtex++(或+1)).

 

2. 测试代码:

#include 
#include 
#include 
#include 
#include 
 
void *tfn(void *arg)
{
    srand(time(NULL));
 
    while(1) 
    {
        printf("hello ");
        sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
        printf("word\n");
        sleep(rand() % 3);
    }
    return NULL;
}
 
int main()
{
    pthread_t tid;
    srand(time(NULL));
 
    pthread_create(&tid, NULL, tfn, NULL);
    while(1) 
    {
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
    }
    return 0;
}

输出结果:

互斥锁、条件变量_第1张图片

 

  3. 测试代码:

#include 
#include 
#include 
#include 
#include 
 
void *tfn(void *arg)
{
    srand(time(NULL));
 
    while(1) 
    {
        printf("hello ");
        sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
        printf("word\n");
        sleep(rand() % 3);
    }
    return NULL;
}
 
int main()
{
    pthread_t tid;
    srand(time(NULL));
 
    pthread_create(&tid, NULL, tfn, NULL);
    while(1) 
    {
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
    }
    return 0;
}

输出结果

互斥锁、条件变量_第2张图片

 

二、 条件变量

条件变量本身不是锁,但它也可以造成线程阻塞,通常与互斥锁配合使用,给多线程提供一个会合的场所。

 

1. 函数原型:

pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t * attr);  //初始化一个条件变量
pthread_cond_destroy(pthread_cond_t *cond);     // 销毁一个条件变量   

pthread_cond_signal(pthread_cond_t *cond);    // 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒全部阻塞在条件变量上的线程                             

 

2. 函数原型:

pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

分析:

函数作用:阻塞等待一个条件变量

  1. 阻塞等待条件变量cond(参数1)满足
  2. 释放已掌握的互斥锁(解锁互斥量),相当于pthread_mutex_unlock(&mutex); {1、2两步为一个原子操作}
  3. 当被唤醒,pthread_cond_wait函数返回,解除阻塞并重新获取互斥锁pthread_mutex_lock(&mutex);

 

3. 函数原型:

    函数作用:限时等待一个条件变量

pthread_cond_timedwait(pthread_cond_t *restrict cond, 
                       pthread_mutex_t *restrict mutex,  
                       const struct timespec *restrict abstime);

参数3:

struct timespec 
{
    time_t tv_sec;  seconds 秒 
    long tv_nsec;    nanoseconds 纳秒
}

你形参:abstime:绝对时间

如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相当当前时间定时1秒钟。

struct timespec t = {1,  0};

pthread_cond_time(&cond, &mutex, &t);只能定时到1970年1月1日00:00:01秒(早已经过去)

正确用法:

time_t cur = time(NULL);   获取当前时间
struct timespec t;         定义timespec结构体变量
t.tv_sec = cur + 1;        定时一秒
pthread_cond_timewait(&cond, mutex, &t) 传参 
 

 

4. 函数原型:

pthread_cond_signal(pthread_cond_t *cond);    // 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒全部阻塞在条件变量上的线程

 

 

三、生产者消费者条件变量模型

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一案例,是比较常见的方法,假定有两个线程,一个模拟生产者行为,一个模拟消费者行为,两个线程同时操作一个共享资源(一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品。

1. 测试代码:

#include  
#include 
#include 
#include 

struct msg
{
    struct msg *next;
    int num;
};

struct msg *head;
struct msg *mp;

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;  //静态初始化:一个条件变量和一个互斥量 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    for (; ;)
    {
        pthread_mutex_lock(&lock);
        while (head == NULL)                           //头指针为空,说明没有节点 可以为if吗 
            pthread_cond_wait(&has_product, &lock);
        mp = head;
        head = mp->next;                               //模拟消费掉一个产品
        pthread_mutex_unlock(&lock);
        printf("-consume-----%d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }
}

void *producer(void *p)
{
    for (; ;) 
    {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;                  //模拟生产一个产品
        printf("-Produce----%d\n", mp->num);

        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);          //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }
}

int main()
{
    pthread_t pid, cid;
    srand(time(NULL));
    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}

输出结果:

互斥锁、条件变量_第3张图片

 【注意】:为什么用while循环而不用if呢?

一个生产者可能对应着多个消费者,生产者向队列中插入一条数据之后发出signal,然后各个消费者线pthread_cond_wait获取mutex后返回,当然,这里只有一个线程获取到了mutex,然后进行处理,其它线程会pending在这里,处理线程处理完毕之后释放mutex,刚才等待的线程中有一个获取mutex,如果这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的。

 

 

五、参考资料

1 深入解析条件变量(condition variables)
 

你可能感兴趣的:(Linux系统编程)