linux——线程通信(2)

1.条件变量

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

1.1 条件变量函数:

pthread_cond_init 函数
pthread_cond_destroy 函数
pthread_cond_wait 函数
pthread_cond_timedwait 函数
pthread_cond_signal 函数
pthread_cond_broadcast 函数
以上 6 个函数的返回值都是:成功返回 0, 失败直接返回错误号。
pthread_cond_t 类型 用于定义条件变量
pthread_cond_t cond;

1.2 pthread_cond_init 函数

初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, 
					  const pthread_condattr_t *restrict attr);
参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

1.3 pthread_cond_destroy 函数

销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

1.4 pthread_cond_wait 函数

阻塞等待一个条件变量

 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

函数作用:

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

1.5 pthread_cond_timedwait 函数

限时等待一个条件变量

int pthread_cond_timedwait(pthread_cond_t *restrict cond, 
							pthread_mutex_t *restrict mutex, 
							const struct timespec *restrict abstime);
参 3: 参看 man sem_timedwait 函数,查看 struct timespec 结构体。
struct timespec {
	time_t tv_sec; /* seconds */long tv_nsec; /* nanosecondes*/ 纳秒
}
形参 abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。

struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)

正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参 APUE.11.6 线程同步条件变量小节
在讲解 setitimer 函数时我们还提到另外一种时间类型:
 struct timeval {
	 time_t tv_sec; /* seconds */ 秒
	suseconds_t tv_usec; /* microseconds */ 微秒
 };

1.6 pthread_cond_signal 函数

唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond);

1.7 pthread_cond_broadcast 函数

唤醒全部阻塞在条件变量上的线程

 int pthread_cond_broadcast(pthread_cond_t *cond);

2.生产者消费者模型

线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。
假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。
两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
#include <stdlib.h>
#include <unistd.h>
#include <pthread.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) { //头指针为空,说明没有节点 可以为 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)
{
        struct msg *mp;
        while (1) {
                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[])
{
        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;
}
zhaoxr@zhaoxr-ThinkPad-E450:~/pthread$ ./pthread_cond 
-Produce ---425
-Consume ---425
-Produce ---486
-Consume ---486
-Produce ---699
-Consume ---699
-Produce ---736
-Consume ---736
-Produce ---452
-Consume ---452
-Produce ---529
-Consume ---529
-Produce ---664
-Consume ---664
-Produce ---340
-Consume ---340
^C

3.条件变量的优点:

相较于 mutex 而言,条件变量可以减少竞争。
如直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,
消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。
有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

4.信号量

进化版的互斥锁(1 --> N)
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,
使用互斥锁是没有办法实现的,只能将整个数据对象锁住。
这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。
线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

5.信号量函数:

sem_init 函数
sem_destroy 函数
sem_wait 函数
sem_trywait 函数
sem_timedwait 函数
sem_post 函数
以上 6 个函数的返回值都是:成功返回 0, 失败返回-1,同时设置 errno。(注意,它们没有 pthread 前缀)
sem_t 类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。
sem_t sem; 规定信号量 sem 不能 < 0。头文件 <semaphore.h>

5.1 信号量基本操作:

sem_wait: 1. 信号量大于 0,则信号量-- (类比 pthread_mutex_lock)
     	  2. 信号量等于 0,造成线程阻塞
sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程 (类比 pthread_mutex_unlock)
但,由于 sem_t 的实现对用户隐藏,所以所谓的++--操作只能通过函数来实现,而不能直接++--符号。
信号量的初值,决定了占用信号量的线程的个数。

5.2 sem_init 函数

初始化一个信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
参 1:sem 信号量
参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
参 3:value 指定信号量初值

5.3 sem_destroy 函数

销毁一个信号量

int sem_destroy(sem_t *sem);

5.4 sem_wait 函数

给信号量加锁 –

int sem_wait(sem_t *sem);

5.5 sem_post 函数

给信号量解锁 ++

int sem_post(sem_t *sem);

5.6 sem_trywait 函数

尝试对信号量加锁 – (与 sem_wait 的区别类比 lock 和 trylock)

int sem_trywait(sem_t *sem);

5.7 sem_timedwait 函数

限时尝试对信号量加锁 –

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参 2:abs_timeout 采用的是绝对时间。
定时 1 秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
t.tv_nsec = t.tv_sec +100; 
sem_timedwait(&sem, &t); 传参

6.信号量举例

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

sem_t sem;
void *func(void* arg){
        int i=(int)arg;
        while(1){
                sem_wait(&sem);
                printf("我是%d,我正在吃饭,当前座位剩余:%d\n",i,sem);
                sleep(2);
                sem_post(&sem);
                sleep(4);
        }
        return NULL;
}

int main()
{
        pthread_t tid[10];
        sem_init(&sem,0,5);
        int i;
        for(i=0;i<10;i++){
                pthread_create(&tid[i],NULL,func,(void*)i);
        }
        for(i=0;i<10;i++){
                pthread_join(tid[i],NULL);
        }
        sem_destroy(&sem);
        return 0;
}
zhaoxr@zhaoxr-ThinkPad-E450:~/pthread$ ./sem
我是0,我正在吃饭,当前座位剩余:4
我是1,我正在吃饭,当前座位剩余:3
我是2,我正在吃饭,当前座位剩余:2
我是3,我正在吃饭,当前座位剩余:1
我是4,我正在吃饭,当前座位剩余:0
我是5,我正在吃饭,当前座位剩余:1
我是8,我正在吃饭,当前座位剩余:2
我是6,我正在吃饭,当前座位剩余:0
我是7,我正在吃饭,当前座位剩余:1
我是9,我正在吃饭,当前座位剩余:0
我是0,我正在吃饭,当前座位剩余:4
我是4,我正在吃饭,当前座位剩余:2
我是1,我正在吃饭,当前座位剩余:3
我是3,我正在吃饭,当前座位剩余:1
我是2,我正在吃饭,当前座位剩余:0
^C

7.生产者消费者信号量模型

使用信号量完成线程间同步,模拟生产者,消费者问题。

【sem_product_consumer.c】
规定: 如果□中有数据,生产者不能生产,只能阻塞。
			如果□中没有数据,消费者不能消费,只能等待数据。
			定义两个信号量:S 满 = 0, S 空 = 1 
			(S 满代表满格的信号量,S 空表示空格的信号量,程序起始,格子一定为空)

所以有:

T 生产者主函数 { 
	 sem_wait(S 空); //空格减1
 	生产....
 	sem_post(S 满); //大饼加1
}
T 消费者主函数 {
	sem_wait(S 满);//大饼减1
	 消费....
 	sem_post(S 空);//空格加1
  }
假设: 线程到达的顺序是:T 生、T 生、T 消。
那么: T 生 1 到达,将 S 空-1,生产,将 S 满+1
T 生 2 到达,S 空已经为 0, 阻塞
T 消 到达,将 S 满-1,消费,将 S 空+1

三个线程到达的顺序是:T 生 1、T 生 2、T 消。而执行的顺序是 T 生 1、T 消、T 生 2
这里,S 空 表示空格子的总数,代表可占用信号量的线程总数-->1。其实这样的话,信号量就等同于互斥锁。
但,如果 S 空=2、3、4……就不一样了,该信号量同时可以由多个线程占用,不再是互斥的形式。
因此我们说信号量是互斥锁的加强版。

8.哲学家吃饭问题

让所有哲学家听到吃饭口令之后,都拿自己右手边的筷子,但是选定某一个哲学家A必须拿自己左手边的筷子;
这样子,A哲学家右边的哲学家B肯定可以第一个吃到饭,吃完饭放下筷子,下面一个人就可以吃饭了;
以此类推。。。

9.共享内存

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

int a=10;
void* func(void* arg)
{
        sleep(1);
        a=100;
        printf("我要修改a\n");
        sleep(2);
        return NULL;
}
int main()
{
        pthread_t tid;
        pthread_create(&tid,NULL,func,NULL);
        while(1){
                printf("%d\n",a);
                sleep(1);
        }
        pthread_join(tid,NULL);
        return 0;
}
zhaoxr@zhaoxr-ThinkPad-E450:~/pthread$ ./pthread_com
10
我要修改a
100
100
100
100
100
100
100
^C
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值