Linux中POSIX IPC(二)和线程同步(2)-学习笔记(十一)

一、条件变量:
1、概念:
用线程间共享的全局变量进行同步的机制。条件变量是用来等待事件。条件变量给多个线程提供了一个汇合的场所。
2、原理:
在线程间同步时,自动阻塞一个线程,直到某些条件满足被唤醒,通常与互斥锁一同被使用。
3、函数:
(1)初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
(2)自动释放mutex锁,等待条件满足
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
1)阻塞等待条件变量的满足(参数1)
  2)释放已掌握的互斥锁,相当于mutex_unlock
  3)当被唤醒的时候,拿锁访问数据(lock),通过wait返回数据,解除阻塞(lock)
(3)销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);  
(4)唤醒阻塞在条件变量上的至少一个线程
int pthread_cond_signal(pthread_cond_t *cond);
(5)唤醒所有阻塞在条件变量上的线程,通知所有人
int pthread_cond_broadcast(pthread_cond_t *cond);
4、程序设计步骤:
(1)定义一把锁 mutex_t mutex;
(2)对锁初始化 init(&mutex);
(3)拿锁 lock
(4)定义条件变量 cond_t has_product;
(5)初始化条件变量 cond_init();
注:
对于多个消费者线程,先拿锁,再访问公共区域。上锁范围:公共数据区域。
条件变量对应在公共区域,当公共区域没有数据时,阻塞在条件变量上等待。
程序:
//程序1 条件变量-生产者消费者模型
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
struct msg {
    struct msg *next;
    int num;
};//定义数据信息结构体

struct msg *head;//定义数据信息结构体指针变量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;//定义并初始化条件变量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义并初始化一把锁
//消费者线程执行的函数
void *consumer(void *p)
{
    struct msg *mp;
    for (;;) 
    {
        pthread_mutex_lock(&lock);//加锁
        while (head == NULL)
            pthread_cond_wait(&has_product, &lock);//自动释放mutex锁,等待条件满足
        mp = head;
        head = mp->next;
        pthread_mutex_unlock(&lock);//解锁
        printf("Consume %d\n", mp->num);
        free(mp);//释放指针
        sleep(rand() % 5);//延时
    }
}
//生产者线程执行的函数
void *producer(void *p)
{
    struct msg *mp;
    //不停的生产
    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(int argc, char *argv[])
{
    //定义2个线程
    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;
}
程序执行效果:

 

二、信号量
1、理解
类比:公共区域变为格子数。生产者空格数减少,消费者空格数增加。
2、作用
信号量用于解决线程信号冲突问题,相当于计数器,允许多个线程同时进入临界区。
3、函数
(1)int sem_init(sem_t *sem,int pshared,unsigned value);//初始化信号量对象
参数1:sem--要进行初始化的信号量对象
参数2:pshared--控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量
参数3:value--赋给信号量对象的一个整数类型的初始值
调用成功时,返回 0;
(2)int  sem_post(sem_t *sem);//给信号量的值+1
参数1:sem--初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
函数功能实现是一个“原子操作”,即同时对同一个信号量做加“1”操作的两个线程是不会冲突的。信号量的值永远会正确地加上一个“2”,因为有两个线程试图改变它。
(3)int  sem_wait(sem_t *sem);//从信号量的值-1,但它永远会先等待该信号量为一个非零值才开始做减法
参数1:sem: 初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
也是一个“原子操作”;
(4)int  sem_destroy(sem_t *sem);//对用完的信号量进行清理
参数1:sem: 初始化的信号量对象的指针,用于改变该对象的值
调用成功时 返回 0;
归还自己占有的一切资源,在清理信号量的时候如果还有线程在等待它,用户就会收到一个错误
程序:
//程序2 信号量-生产者消费者模型
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

#define NUM 5//宏定义
int queue[NUM];//整型数组

sem_t blank_number, product_number;//定义生产者信号量和消费者信号量
//生产者线程执行的函数
void *producer(void *arg)
{
    int p = 0;
    while (1) 
    {
        sem_wait(&blank_number);//生产者信号量的值-1
        queue[p] = rand() % 1000 + 1;
        printf("Produce %d\n", queue[p]);
        sem_post(&product_number);//消费者信号量的值+1
        p = (p+1)%NUM;
        sleep(rand()%5);
    } 
}
//消费者线程执行的函数
void *consumer(void *arg)
{
    int c = 0;
    while (1) 
    {
        sem_wait(&product_number);//消费者信号量的值-1
        printf("Consume %d\n", queue[c]);
        queue[c] = 0;
        sem_post(&blank_number);//生产者信号量的值+1
        c = (c+1)%NUM;
        sleep(rand()%5);
    }
}

int main(int argc, char *argv[])
{
    pthread_t pid, cid; //定义2个线程
    //初始化生产者信号量对象 值为5
    sem_init(&blank_number, 0, NUM);
    //初始化消费者信号量对象 值为0
    sem_init(&product_number, 0, 0);   
    
    pthread_create(&pid, NULL, producer, NULL);//创建生产者线程
    pthread_create(&cid, NULL, consumer, NULL);//创建消费者线程
    pthread_join(pid, NULL); //回收生产者线程
    pthread_join(cid, NULL); //回收消费者线程
    
    //清理消费者信号量对象
    sem_destroy(&blank_number);
    //清理生产者信号量对象
    sem_destroy(&product_number);
    return 0;
}
程序执行效果:

 

三、条件变量封装函数设计
//程序3 条件变量封装函数设计1
#ifndef CPTHREADCOND_H
#define CPTHREADCOND_H

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include "wild.h"

class CPThreadCond
{
public:
    CPThreadCond();
    ~ CPThreadCond();
    bool wait();
    bool signal();
    bool broadcast();
protected:
    pthread_cond_t m_cond;//定义条件变量
    pthread_mutex_t m_mutex;//定义一把锁
};

#endif // CPTHREADCOND_H
//程序4 条件变量封装函数设计2
#include "cpthreadcond.h"

CPThreadCond::CPThreadCond()//构造函数
{
    m_cond=wild::has_product;//条件变量
    m_mutex=wild::lock;//一把锁
}

CPThreadCond::~CPThreadCond()//析构函数
{
    //销毁条件变量
    if (pthread_cond_destroy(&m_cond))
    {
        perror("m_cond destroy\n");
    }
    //销毁锁
    if(pthread_mutex_destroy(&m_mutex) !=0)
    {
        perror("mutex destroy\n");
    }
}

bool CPThreadCond::wait()
{
    //自动释放mutex锁,等待条件满足
    int ret = pthread_cond_wait(&m_cond,&m_mutex);
    if(ret !=0)
    {
        perror("mutex lock\n");
    }
    return ret==0 ?true:false;
}

bool CPThreadCond::signal()
{
    //唤醒阻塞在条件变量上的至少一个线程
    int ret = pthread_cond_signal(&m_cond);
    if(ret !=0)
    {
        perror("m_cond send\n");
    }
    return ret==0 ? true:false;
}

bool CPThreadCond::broadcast()
{
    //唤醒所有阻塞在条件变量上的线程,通知所有人
    int ret = pthread_cond_broadcast(&m_cond);
    if (ret != 0)
    {
        perror("m_cond send\n");
    }
    return ret == 0 ? true : false;
}
//程序5 条件变量封装函数设计3
#ifndef WILD_H
#define WILD_H

#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>

class wild
{
public:
    wild();
    static pthread_cond_t has_product;//定义条件变量
    static pthread_mutex_t lock;//定义一把锁
};

#endif // WILD_H
//程序6 条件变量封装函数设计4
#include "wild.h"
//静态变量初始化
pthread_cond_t wild::has_product = PTHREAD_COND_INITIALIZER;//定义条件变量
pthread_mutex_t wild::lock = PTHREAD_MUTEX_INITIALIZER;//定义一把锁

wild::wild()
{
    
}
//程序7 条件变量封装函数设计5
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include "cpthreadcond.h"
#include "wild.h"

struct msg {
    struct msg *next;
    int num;
};//定义数据信息结构体

struct msg *head;//定义数据信息结构体指针
CPThreadCond cond;//定义条件变量类对象
//消费者线程执行的函数
void *consumer(void *p)
{
    struct msg *mp;
    for (;;)
    {
        pthread_mutex_lock(&(wild::lock));
        while (head == NULL)
            cond.wait();//自动释放mutex锁,等待条件满足
        mp = head;
        head = mp->next;
        pthread_mutex_unlock(&(wild::lock));
        printf("Consume %d\n", mp->num);
        delete mp; //释放结点
        sleep(rand() % 5);
    }
}

//生产者线程执行的函数
void *producer(void *p)
{
    struct msg *mp;
    //不停的生产
    for (;;)
    {
        //创建节点
        mp = new (struct msg);
        mp->num = rand() % 1000 + 1;//随机生成数据信息
        printf("Produce %d\n", mp->num);
        pthread_mutex_lock(&(wild::lock));//加锁
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&(wild::lock));//解锁
        //唤醒阻塞在条件变量上的至少一个线程
        cond.signal();
        sleep(rand() % 5);
    }
}
int main(int argc, char *argv[])
{
    //定义2个线程
    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;
}


程序执行效果:

 

四、线程同步总结:
线程同步实质为线程排队,只有共享资源的读写访问才需要线程同步。
1、线程同步方式: 互斥锁(也称为互斥量);条件变量;信号量。
2、互斥量:(mutex)
 (1)概述
    互斥锁用于同步线程对共享数据的访问,即实现多线程程序中的同步访问的另一种手段。
 (2)特点
    本质是一把锁,访问共享资源时对互斥量设置,即加锁,访问结束,进行解锁。在加锁之后,其他线程试图对该互斥量设置时会被阻塞,直到当前进程释放互斥锁。
3、条件变量:(cond)
 (1)概述:利用线程间共享的全局变量进行线程同步,互斥量解锁和条件变量挂起自动进行。条件变量在线程间同步时,自动阻塞一个线程,直到某些条件满足被唤醒,通常与互斥锁一同被使用。
 (2)作用和特点:
    1)条件变量用于线程之间同步共享数据的值,给多个线程提供一个汇合的场所。
    2)条件变量分为条件和变量,条件由互斥量保护,线程在改变条件状态之前必须先锁住条件变量。
    3)条件变量可使线程阻塞等待某种条件出现。
 (3)与互斥量区别:
    条件变量用来等待而非上锁,可以使线程睡眠等待某种条件出现,即自动阻塞一个线程,直到特殊情况发生为止,通常条件变量与互斥锁同时使用。
 (4)两个动作:
    1)一个线程等待“条件变量条件成立”而挂起;
    2)另一线程让条件成立,即发出条件成立信号。
 (5)条件检测:
    条件检测在互斥锁保护下进行,例如线程1检测到条件为假,线程1自动阻塞,并释放等待状态的互斥锁。
    当线程2改变了条件,则会发出信号给关联的条件变量,唤醒一个或多个等待它的线程,比如线程1将重新获得互斥锁,重新检测条件变量。
4、信号量:(sem)
(1)概述
   本质上是一个非负的整数计数器,用于对公共资源访问,与进程一样,线程也可通过信号量实现通信。
(2)特点
   1)信号量是一个特殊的变量,可被增加和减少,对其关键访问保证是原子操作,即多个线程试图改变信号量值,系统保证所有操作依次进行,不会发生线程冲突。
   2)信号量一般用于保护一段代码,每次只能被一个程序执行。等待信号量时给信号量减1,直到信号量值大于0,释放信号量,信号量值加1,并通知其他等待线程。
编程时可根据操作信号值的结果判断是否对公共资源有访问权限,信号量值大于0 ,可访问,否则阻塞,PV原语为对信号量操作,一次P操作使信号量-1,一次V操作使信号量值+1。
(3)分类:
  1)二进制信号量--“0”和“1”取值
  2)计数信号量--取值范围较大,用于有限个线程执行一段程序
(4)与条件变量的区别
信号量递增和减少会被系统自动记住,系统内部存在一个计数器实现信号量而不必担心信号量值丢失。当唤醒一个条件变量时,如果没有相应的线程在等待该条件变量,此次唤醒将被丢失。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用 POSIX 信号量实现多个线程同时访问共享资源的例子: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define MAX_THREADS 10 #define NUM_ITERATIONS 5 sem_t sem; // 定义信号量 int shared_resource = 0; // 共享资源 void *thread_func(void *arg) { int i; int id = *((int *) arg); for (i = 0; i < NUM_ITERATIONS; i++) { sem_wait(&amp;sem); // 等待信号量 shared_resource++; // 访问共享资源 printf("Thread %d updated shared_resource to %d\n", id, shared_resource); sem_post(&amp;sem); // 释放信号量 } pthread_exit(NULL); } int main(int argc, char *argv[]) { int i; pthread_t threads[MAX_THREADS]; int thread_ids[MAX_THREADS]; sem_init(&amp;sem, 0, 1); // 初始化信号量 for (i = 0; i < MAX_THREADS; i++) { thread_ids[i] = i; pthread_create(&amp;threads[i], NULL, thread_func, (void *) &amp;thread_ids[i]); } for (i = 0; i < MAX_THREADS; i++) { pthread_join(threads[i], NULL); } sem_destroy(&amp;sem); // 销毁信号量 return 0; } ``` 在上面的例子,我们创建了多个线程,每个线程都会访问共享资源 shared_resource。为了避免多个线程同时访问该资源,我们使用了一个信号量 sem,只有获得了该信号量的线程才能访问 shared_resource。在每个线程访问 shared_resource 之前,它会调用 sem_wait() 等待信号量,表示它要访问 shared_resource 了。在访问完 shared_resource 后,线程会调用 sem_post() 释放信号量,表示它已经访问完了 shared_resource,其他线程可以开始访问了。 需要注意的是,在上面的例子,我们使用了互斥信号量,即 sem 的初始值为 1。这意味着同一时刻只有一个线程可以访问 shared_resource。如果想要多个线程同时访问 shared_resource,可以使用非互斥信号量,即将 sem 的初始值设置为大于 1 的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值