线程同步

在Linux系统中,如果有多个线程并发运行,就需要考虑线程同步的问题。比如下面一道笔试题:

两个等价线程并发的执行下列程序,a为全局变量,初始为0,假设printf、++、--操作都是原子性的,则输出不肯哪个是(A)

void foo() {

    if(a <= 0) {

        a++;

    }

    else {

        a--;

    }

    printf("%d", a);

}


A.01

B.10

C.12

D.22

答案是A。但是为什么会出现像C或D那样的结果呢?

假设两个线程分别为线程1和线程2。线程1先运行,在执行完第三行后,线程2运行,这时候变量a任然是0,所以线程2会执行if语句,之后输出结果“1”,这时候线程1继续运行,继续给变量a加1,之后输出结果“2”,所以就出现了C选项。

在多线程环境中,要确保线程对共享变量的操作结果的确定性。不允许两个以上线程同时修改一个变量,这就涉及到了线程的同步。有以下方法确保线程的同步:

1、互斥量

互斥量本质上是一把锁。要访问公共资源时,先对互斥量加锁,在访问结束后,释放互斥量上面的锁。对互斥量加锁后,如果再有线程对互斥量加锁,这个线程会阻塞,直到当前进程释放互斥量上面的锁。如果有多个线程因为对互斥量二次加锁而阻塞,那么这些线程会同时变为运行,第一个调度的线程再次对互斥量加锁。
要想避免上面的结果,就要确保foo()函数执行的原子性,通过互斥量可以保证。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

int a=0;
pthread_mutex_t counter_mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥量并初始化
 
void *foo(void *);

int main(int argc, char **argv)
{
pthread_t tidA,tidB;

pthread_create(&tidA, NULL, foo, NULL);//创建线程
pthread_create(&tidB, NULL, foo, NULL);
 
pthread_join(tidA,NULL);//等待线程执行结束
pthread_join(tidB,NULL);
pthread_mutex_destroy(&counter_mutex);//销毁互斥量
return 0;
}

void *foo(void *vptr)
{
		pthread_mutex_lock(&counter_mutex);//给互斥量加锁
		if(a<=0){		
			a++;
		}
		else{
			a--;
		}
	 
	 printf("%d",a);
 	 pthread_mutex_unlock(&counter_mutex);//释放互斥量上面的锁
}

如果想再给互斥量加锁时不要阻塞,可以使用
pthread_mutex_trylock
如果互斥量未加锁,它会给互斥量加锁;如果互斥量已经加锁,它会立刻返回EBUSY。
如果一个线程试图给同一个互斥量加锁2次,那么这个线程会阻塞;如果要给多个互斥量加锁,要确保加锁顺序的一致性,否则很容易造成死锁。

2、条件变量

在多线程中环境中,可能会有这样的情景:线程1要等待一个条件A发生,当条件A发生后,线程1才可以执行;而线程2在执行过程中会使条件A发生。这时就要用到条件变量了。
条件变量常常和互斥量一起使用,因为互斥量要保护条件变量。线程在改变条件变量前,必须给互斥量加锁,防止多个线程同时请求信号量。
借助《Linux C编程一站式学习》的生产者消费者模型来说明:生产者生产货物,把货物放到仓库(一个链表),消费者从仓库消费货物(前提时有货物的情况下);如果没有货物,消费者要等待生产者生产货物这个条件,消费者知道这个条件发生后才可以消费货物。生产者和消费者不能同时使用仓库。
#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);
		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)
{
pthread_t tidA,tidB;
pthread_create(&tidA, NULL, producer, NULL);
pthread_create(&tidB, NULL, consumer, NULL);

pthread_join(tidA,NULL);
pthread_join(tidB,NULL);

return 0;
}

pthread_cond_wait
的第二个参数是锁住的互斥量。这个函数的功能是把线程放到等待队列上然后对互斥量解锁,这两步操作是一个原子操作。如果在这一步不释放锁,那么生产者就无法对链表就行操作。在条件成立后,这个函数返回,同时锁住互斥量。
pthread_cond_timewait
多了个time,可以设定一个时间值,过了这个时间值,条件还没成立也会返回。

pthread_cond_signal(&has_product)
将唤醒等待该条件的某个线程,而

pthread_cond_broadcast(&has_product)
将唤醒等待该条件的所有线程。

3、信号量

首先要说明的是,信号量不仅可以用于线程同步,也可以用于进程间的通信。
信号量和互斥量有点类似,互斥量可以看做是资源可用数,其值为1,当互斥量上锁以后,值为0,资源不可用。信号量可以看做是资源数可以大于1的互斥量。
当线程/进程使用信号时:
(1)测试信号量的值
(2)如果值为正,则该资源可用,给信号量的值减1,占用一个资源
(3)如果为负,则线程/进程进入休眠状态,直至信号量大于0,被唤醒,然后转到第(1)步
还是借助《Linux C编程一站式学习》的生产者消费者模型来说明,这是仓库基于循环队列。
#include<stdlib.h>
#include<pthread.h>
#include<stdio.h>
#include<semaphore.h>

#define NUM 5;
int queue[5];
sem_t blank_number,product_number;

void *producer(void *arg)
{
	int p=0;
	while(1){
		sem_wait(&blank_number);
		queue[p]=rand()%1000+1;
		printf("Produce %d\n",queue[p]);
		sem_post(&product_number);
		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()
{
	pthread_t pid,cid;

	sem_init(&blank_number,0,5);//对于生产者来说,仓库是空的,可以用的有5个
	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;
}
sem_trywait
是试图给信号量减1,如果信号量为负,并不会阻塞,而是立即返回。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值