线程同步与互斥

互斥:多线程环境下,当所有线程同时访问共享数据时,可能产生冲突,需要使在任一时刻有且只有一个线程访问其共享数据,保证其原子性。

线程中实现互斥可运用互斥锁(mutex)来实现,其可相当于进程之间的二元信号量:

函数如下:

#include <pthread.h>
//定义初始化一把互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;   //全局、静态用宏初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  //运用函数进行初始化

参数:
mutex:传入定义锁地址;
attr:互斥锁属性,一般设为NULL(默认属性).

int pthread_mutex_destroy(pthread_mutex_t* mutex);   //销毁mutex锁
int pthread_mutex_lock(pthread_mutex_t* mutex);  //加锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);  //解锁
int pthread_mutex_trylock(pthread_mutex_t* mutex);  //尝试去加锁
返回值:成功返回0,失败返回错误号。

注意:一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行;
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。


举例:此处有两个线程对同一全局变量进行同一操作各自累加5000次,正确结果应为10000,若两线程不互斥访问,代码如下:

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

int glo_val=0;
//pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;   //创建锁 

void* thread_run(void* arg)
{
	int i=0;
	while(i<5000)
	{
		//glo_val++;
	
		//pthread_mutex_lock(&lock);    //加锁 
		int tmp=glo_val;
		printf("tid:%u glo_val:%d\n",pthread_self(),glo_val);  //从用户态切换到内核态,触发线程间频繁切换
		glo_val=tmp+1;
		//pthread_mutex_unlock(&lock);   //解锁 

		++i;
	}
	
	printf("tid:%u glo_val:%d\n",pthread_self(),glo_val);
	return (int*)0;
}
int main()
{
	pthread_t tid1;
	pthread_t tid2;
	pthread_create(&tid1,NULL,thread_run,NULL);
	pthread_create(&tid2,NULL,thread_run,NULL);

	void* ret1;
	void* ret2;
	pthread_join(tid1,&ret1);
	pthread_join(tid2,&ret2);
	//pthread_mutex_destroy(&lock);    //销毁锁 

	printf("glo_val:%d\n",glo_val);    
	return 0;
}
实际结果如下:


结果错误,若取消注释,加上互斥锁,结果正确,如下:


以上第一次没有互斥之所以错误是由于:
若对一个变量增加1,这个操作在需要三条指令完成:
1. 从内存读变量值到寄存器;
2. 寄存器的值加1;
3. 将寄存器的值写回内存;
当两个线程同时都对同一变量做累加操作时,如图:


以上原因致使线程2做了无用功。
解决以上原因就运用互斥锁,其原理如下:

lock:
        movb $0,%al;
        xchgb %al,mutex;    //原子操作
        if(a1(寄存器值)>0)
        {
              return 0;
         }
        else
        {
              //挂起等待
         }
         goto lock;

unlock:
          movb $1,mutex;
          //唤醒等待mutex的线程;
          return 0;
无论多少个线程去申请锁时 ,只要其中一个线程执行了xchgb %al,mutex此原子操作(即执行此语句绝对不会被中途切出,只有不执行与执行两种状态),将锁值变为0,在它没有释放此锁期间,无论它切出多少次,都将一直拥有锁,其它线程申请mutex必定为0,不会拥有锁,直至当前线程释放锁。

以下解释同步:
饥饿:即一个线程在申请资源时由于其优先级或其它问题使其一直申请不到资源,造成其饥饿问题。
同步:解决饥饿问题,以互斥为前提,使不同线程间访问资源时以特定的顺序去访问。
线程间可运用条件变量来实现同步机制:(以此可用互斥锁与条件变量来实现消费者生产者模型,下个博客实现)
函数如下:
1.定义初始化条件变量:

#include <pthread.h>
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;   //宏初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);  //函数初始化
参数:

restrict cond:条件变量
restrict attr:条件变量属性,一般设为NULL默认属性
2.销毁条件变量:

int pthread_cond_destroy(pthread_cond_t* cond);
3.等待:

int pthread_cond_waut(pthread_cond_t *restrict_cond,pthread_mutex_t* restrict_mutex);  //阻塞等待
int pthread_cond_timedwait(pthread_cond_t *restrict_cond,pthread_mutex_t* restrict_mutex);  //一定时间唤醒
参数:分别为当前条件变量与当前互斥锁(操作释放当前互斥锁)
4.唤醒通知:
int pthread_cond_signal(pthread_cond_t* cond);//唤醒一个线程
iny pthread_cond_broadcast(pthread_cond_t* cond);  //唤醒全部线程
参数:当前条件变量
返回值:成功返回0,失败返回错误号。

注意:一个条件变量总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个条件变量上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex;
2. 阻塞等待;
3. 当被唤醒时,重新获得Mutex并返回;
pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。
一个线程可以调用pthread_cond_signal唤醒在某个条件变量上等待的另一个线程,也可以调pthread_cond_broadcast唤醒在这个条件变量上等待的所有线程。

你可能感兴趣的:(线程同步与互斥)