Linux下线程的同步与互斥

Linux下线程的同步与互斥

  • 线程间通信
    • 同步
      • 信号量
      • P操作(P(S)):
      • V操作(V(S)):
      • Posix信号量
      • pthread库常用的函数
        • sem_init函数
        • P操作和V操作的函数
        • 线程同步的示例
          • 示例(生产者/消费者问题)
    • 互斥
      • 临界资源
      • 临界区
      • 互斥机制
      • pthread提供的函数
        • 互斥锁初始化函数——pthread_mutex_init
        • 申请锁函数——pthread_mutex_lock
        • 释放锁函数——pthread_mutex_unlock
        • 线程互斥示例

线程间通信

	线程共享同一进程的地址空间

优点:

  • 通过全局变量交换数据

缺点:

  • 多个线程访问共享全局数据时需要同步或者互斥

同步

同步指的是多个任务按照约定的先后次序互相配合完成一件事情。

1968年,Edsgar Dijkstra基于信号量的概念提出了一种同步机制。由信号量来决定线程是继续运行还是阻塞等待。

信号量

  • 信号量代表某一类资源,其值表示系统中该资源的数量。
  • 信号量是一个受保护的变量,只能通过三种操作来访问。
    > 初始化
    > P操作(申请资源)
    > V操作(释放资源)

P操作(P(S)):

if(信号量的值大于0){
申请资源的任务继续运行;
信号量的值减一;
}
else{
申请资源的任务阻塞;
}

V操作(V(S)):

信号量的值加一;
if(有任务在等待资源){
唤醒等待的任务,让其继续运行;
}

Posix信号量

  • Posix中定义了两种信号量:

    >无名信号量(基于内存的信号量)
    >有名信号量

下面将介绍无名信号量。

pthread库常用的函数

sem_init函数

#include 
int sem_init(sem_t* sem,int pshared,unsigned int val);
  • 函数调用成功时返回0,失败时返回EOF
  • 第一个参数(sem):指向要初始化的信号量对象
  • 第二个参数(pshared): 0——线程间 1——进程间
  • 第三个参数(val):信号量初值

P操作和V操作的函数

#include 
int sem_wait(sem_t* sem); //P操作
int sem_post(sem_t* sem); //V操作
  • 函数调用成功时返回0,失败时返回EOF
  • 参数(sem):指向要操作的信号量对象

线程同步的示例

示例(生产者/消费者问题)

两个线程同步读写缓冲区

Created with Raphaël 2.2.0 写线程 写操作 缓冲区 读操作 读线程

这个问题需要两个信号量实现读写的同步,一个信号量来描述可读缓冲区的数量,一个来描述可写缓冲区的数量。

#include 
#include 
#include 
#include 

char buf[32];
sem_t r;
sem_t w;
void* function(void* arg);

int main(void)
{
     
	pthread_t a_thread;
	if(sem_init(&r,0,0)<0)	//创建可读缓冲区信号量,初始为0
	{
     
		perror("sem_init");
		exit(-1);
	}
	if(sem_init(&w,0,1)<0)	//创建可写缓冲区信号量
	{
     
		perror("sem_init");
		exit(-1);
	}
	if(pthread_create(&a_thread,NULL,function,NULL)!=0)
	{
     
		printf("fail to pthread_create");
		exit(-1);
	}
	printf("input 'quit' to exit\n");
	do
	{
     
		sem_wait(&w);
		fgets(buf,32,stdin);
		sem_post(&r);
	}while(strncmp(buf,"quit",4)!=0);
	return 0;
}
void* function(void* arg)
{
     
	while(1)
	{
     
		sem_wait(&r);
		printf("you enter %d characters\n",strlen(buf));
		sem_post(&w);
	}
}

结果为
Linux下线程的同步与互斥_第1张图片
在写这段代码时,首先要主要到,创建信号量应在创建线程之前完成。防止出现还未创建信号量主线程的CPU时间片用完导致执行了新的线程,发生访问冲突。

互斥

临界资源

  • 一次只允许一个任务(进程、线程)访问的共享资源

临界区

  • 访问临界区的代码

互斥机制

  • mutex互斥锁
  • 任务访问临界资源之前申请锁,访问完后释放锁

pthread提供的函数

互斥锁初始化函数——pthread_mutex_init

#include 
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁
  • 函数调用成功时返回0,失败时返回错误码
  • 第一个参数(mutex):指向要初始化的互斥锁对象
  • 第二个参数(attr):互斥锁属性,NULL表示默认属性
    或者你可以使用宏来静态初始化锁变量。

互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前有四个值可供选择:

enum
{
     
  PTHREAD_MUTEX_TIMED_NP,
  PTHREAD_MUTEX_RECURSIVE_NP,
  PTHREAD_MUTEX_ERRORCHECK_NP,
  PTHREAD_MUTEX_ADAPTIVE_NP
};
  • PTHREAD_MUTEX_TIMED_NP,这是默认值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  • PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  • PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK(死锁),否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
  • PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
    通常我们使用默认的普通锁

申请锁函数——pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 函数调用成功时返回0,失败时返回错误码
  • mutex参数:指向要初始化的互斥锁对象
  • 如果无法获得锁,任务阻塞

释放锁函数——pthread_mutex_unlock

int
__pthread_mutex_unlock (pthread_mutex_t *mutex)
  • 函数调用成功时返回0,失败时返回错误码
  • mutex参数:指向要初始化的互斥锁对象
  • 执行完临界区要及时释放锁

线程互斥示例

#include 
#include 
#include 
#include 

unsigned int count,value1,value2;
pthread_mutex_t lock;

void* function(void* arg);

int main(void)
{
     
	pthread_t a_thread;
	if(pthread_mutex_init(&lock,NULL)!=0)
	{
     
		printf("fail to pthread_mutex_init\n");
		exit(-1);
	}
	if(pthread_create(&a_thread,NULL,function,NULL)!=0)
	{
     
		printf("fail to pthread_create\n");
		exit(-1);
	}
	while(1)
	{
     
		count++;
#ifdef _LOCK_
		pthread_mutex_lock(&lock);
#endif
		value1 = count;
		value2 = count;
#ifdef _LOCK_
		pthread_mutex_unlock(&lock);
#endif
	}
	return 0;
}
void* function(void* arg)
{
     
	while(1)
	{
     
#ifdef _LOCK_
		pthread_mutex_lock(&lock);
#endif
		if(value1 != value2)
		{
     
			printf("value1 = %u, value2 = %u\n",value1,value2);
			usleep(100000);
		}
#ifdef _LOCK_
		pthread_mutex_unlock(&lock);
#endif
	}
	return NULL;
}

首先我们看不加锁的执行情况,我们可以发现会出现value1与value2不同情况。
Linux下线程的同步与互斥_第2张图片
枷锁之后的结果
在这里插入图片描述
我们可以分析出如果不加锁,那么当一个进程在执行value1 = count;就可能发生时间片到执子线程,而此时可能value2还未被赋值,所以value1!=value2
这时我们使用互斥锁便能很好的解决同时访问临界区的问题。
如果你看到这觉得写得不错,那么可以帮我点一个赞再走吧^.^

你可能感兴趣的:(linux,多线程)