两个线程并行对全局变量count++ (采用一个val值作为中间变量,模拟寄存器工作方式,后面会详解)
#include
#include
#include
#define NLOOP 5000 //循环次数
int count = 0;//全局资源
void* func(void* p)
{
int i,val;
for (i = 0; i < NLOOP; i++)
{
val = count;
printf("count = %d\n",val+1);
count = val + 1;
usleep(100);//减缓线程执行速度,增加资源冲突概率
}
return NULL;
}
int main()
{
pthread_t tidA, tidB;
pthread_create(&tidA, NULL, &func, NULL);//线程A 对count++
pthread_create(&tidB, NULL, &func, NULL);//线程B 对count++
sleep(1);
pthread_join(tidA, NULL);
pthread_join(tidB, NULL);
return 0;
}
第一次执行结果
第二次执行结果
第三次执行结果
一段代码执行三次出现不同的结果,这是为什么?就是因为两个线程同时对共享资源进行操作,导致CPU处理共享资源出现错误
寄存器处理数据+1操作一般分为三步
假设变量值为5
那么CPU执行线程A,将变量读到寄存器中,寄存器的值+1(正在加,此时值还为5),同时线程B将变量从内存中读走(值也为5),线程A将寄存器值写回变量(此时值为6),之后线程B处理完写回变量(此时值还是为6)
简单理解为:
超市中的储存柜,当有人使用储存柜后,其他人使用不了,只能等待使用者使用完,再使用
#include
//pthread_mutex_t是锁类型,用来定义互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//互斥锁的初始化
//restrict,C语言中的一种类型限定符,用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。 第二个参数一般为NULL
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//判断是否上锁
//返回值:0表示已上锁,非0表示未上锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
对之前的例子加上互斥量
#include
#include
#include
#define NLOOP 5000 //循环次数
int count = 0;//全局资源
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;//定义锁
void* func(void* p)
{
int i, val;
for (i = 0; i < NLOOP; i++)
{
pthread_mutex_lock(&counter_mutex);//上锁
val = count;
printf("count = %d\n", val + 1);
count = val + 1;
pthread_mutex_unlock(&counter_mutex);//解锁
usleep(100);//减缓线程执行速度,增加资源冲突概率
}
return NULL;
}
int main()
{
pthread_t tidA, tidB;
pthread_create(&tidA, NULL, &func, NULL);//线程A 对count++
pthread_create(&tidB, NULL, &func, NULL);//线程B 对count++
sleep(1);
pthread_join(tidA, NULL);
pthread_join(tidB, NULL);
return 0;
}
#include
//全局定义条件变量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
//初始化条件变量
//cond参数为条件变量指针,通过该函数实现条件变量赋初值;cond_attr参数通常为NULL
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
//销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
//自动释放mutex锁,等待条件满足
//这个函数的过程我们必须了解,首先对互斥锁进行解锁;然后自身堵塞等待;当等待条件达成,注意这时候函数并未返回,而是重新获得锁并返回。
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
//自动释放mutex锁,等待条件满足,如果在abstime时间内还没有满足,则返回错误
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
//让等待条件满足的线程中某一个被唤醒
int pthread_cond_signal(pthread_cond_t *cond);
//让等待条件满足的线程中全部被唤醒 (广播)
int pthread_cond_broadcast(pthread_cond_t *cond);
流程
首先消费者需要访问共享资源首先要去拿到锁访问条件变量,条件变量说现在还没有资源,所以消费者释放锁,阻塞等待,直到生产者生产出资源后,将资源先放到公共区后,再告诉条件变量,现在有资源了,然后条件变量再去唤醒阻塞线程,这些阻塞的线程被唤醒后需要去挣抢锁,先拿到锁的线程优先访问共享资源
实例
#include
#include
#include
typedef struct msg {
struct msg *next;
int num;
}MSG_T;
MSG_T *head;//消息头结点
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//互斥锁
//pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //条件变量 本文通过init定义条件变量
pthread_cond_t mycond;
//生产者
void *producer(void *p)
{
MSG_T* mp;
for (;;) {
mp = malloc( sizeof(MSG_T) );
mp->num = rand() % 1000 + 1;//生成随机数1到1000
printf("Produce %d\n", mp->num);
//将资源放入公共区
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
//通知条件变量唤醒线程
pthread_cond_signal(&mycond);
sleep(rand() % 5);
}
}
//消费者
void *consumer(void *p)
{
MSG_T* mp;
for (;;) {
pthread_mutex_lock(&lock);//上锁
while (head == NULL)//当没有数据时,wait阻塞等待
pthread_cond_wait(&mycond, &lock);//当没有拿到锁的时候 释放锁并等待
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);//解锁
printf("Consume %d\n", mp->num);
free(mp);
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
if (pthread_cond_init(&mycond, NULL) != 0)
{
printf("cond error'\n");
exit(1);
}
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;
}
简单理解:
假设现在有五把钥匙,当有人拿走一把后(P操作),就剩4把钥匙,直到钥匙被拿完,后面的人需要等待,直到使用者将钥匙归还(V操作)
#include
//初始化
//sem: 要进行初始化的信号量对象
//pshared:控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量
//value:赋给信号量对象的一个整数类型的初始值调用成功时 返回 0;
int sem_init(sem_t *sem,int pshared,unsigned value);
//p操作 -1
int sem_wait(sem_t *sem);
//v操作 +1
int sem_post(sem_t *sem);
//销毁信号量
int sem_destory(sem_t *sem);
信号量的生产消费者模型
#include
#include
#include
#include
#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);//信号量blank_number--,可以比喻成盘子(最多5个盘子)盘子数量-1,
queue[p] = rand() % 1000 + 1;//产生随机数
printf("Produce %d\n", queue[p]);
sem_post(&product_number);//信号量product_number++ ,比喻成菜,现在菜的数量+1 消费者可以使用
p = (p + 1) % NUM;//队列下标偏移
sleep(rand() % 5);
}
}
//消费者
void *consumer(void *arg)
{
int c = 0;
while (1) {
sem_wait(&product_number);//信号量product_number--,现在菜的数量-1
printf("Consume %d\n", queue[c]);
queue[c] = 0;//清空当前下标队列
sem_post(&blank_number);//信号量 blank_number++,资源使用完了 盘子数量+1
c = (c + 1) % NUM;//队列下标偏移
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
sem_init(&blank_number, 0, NUM);//值为5
sem_init(&product_number, 0, 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;
}