Linux学习_线程信号量

Posted Leslie X徐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习_线程信号量相关的知识,希望对你有一定的参考价值。

线程同步——信号量

信号量

  1. 概念
  • 可以以图书馆借阅和归还为类比,借阅则书目减1,归还则书目加1,若书目为0则已借阅完不可再借阅。
  • 信号量从本质上是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问。
  • 信号量可以实现线程的同步和互斥。
  • 通过sem_post()和sem_wait()函数对信号量进行加减操作从而解决线程的同步和互斥。
  • 信号量数据类型:
    • sem_t
  1. 信号量的创建和销毁
#include <semaphore.h>

int  sem_init(sem_t* sem, int pshared, unsigned value);

int sem_destroy(sem_t* sem);

  • 返回:成功返回0,出错返回错误编号
  • 参数:
    • sem:信号量指针。
    • pshared:是否在进程间共享(跨进程共享)的标志,0为不共享,1为共享。
    • value:信号量的初始值。
  1. 信号量的加减操作
#include <semaphore.h>

//功能:增加信号量的值
int sem_post(sem_t* sem);

//功能:减少信号量的值
int sem_wait(sem_t* sem);

//功能:sem_wait()的非阻塞版本
int sem_trywait(sem_t* sem);

  • 返回:成功返回0,出错返回错误编号
  • 调用sem_post()一次,信号量做加1操作
  • 调用sem_wait()一次,信号量做减1操作
  • 当线程调用sem_wait()后,若信号量的值小于0则线程阻塞。只有其他线程在调用sem_post()对信号量做加操作后并且其值大于或等于0时,阻塞的线程才能继续运行。
  1. 案例:
    创建a,b,c线程,运行顺序为c,b,a
/*
 * sem_test.c
 * 创建a,b,c线程,运行顺序为c,b,a
 */

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

//定义线程信号量
sem_t	sem1;	//a和b之间信号量
sem_t	sem2;	//b和c之间信号量

//定义线程运行函数
void*	a_fn(void* arg)
{
	sem_wait(&sem1);
	printf("thread a running\\n");
	return (void*)0;
}

void*	b_fn(void* arg)
{
	sem_wait(&sem2);
	printf("thread b running\\n");
	//释放线程a
	sem_post(&sem1);
	return (void*)0;
}

void*	c_fn(void* arg)
{
	printf("thread c running\\n");
	/*
	 * 释放线程b(对线程信号量sem2加1操作
	 * 让阻塞的线程b继续运行)
	 */
	sem_post(&sem2);
	return (void*)0;
}


//主函数
int main(int argc, char **argv)
{
	//定义三个线程标识符
	pthread_t	a, b, c;
	
	//线程信号量的初始化,不共享,初始值为0
	sem_init(&sem1, 0, 0);
	sem_init(&sem2, 0, 0);
	
	//创建线程
	pthread_create(&a, NULL, a_fn, (void*)0);
	pthread_create(&b, NULL, b_fn, (void*)0);
	pthread_create(&c, NULL, c_fn, (void*)0);
	
	//终止线程
	pthread_join(a,NULL);
	pthread_join(b,NULL);
	pthread_join(c,NULL);
	
	//销毁信号量
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	
	
	return 0;
}


输出:

thread c running
thread b running
thread a running

原语:PV操作

  1. PV操作概念
  • P操作:减一个步长

  • V操作:加一个步长

  • 例:

    • sem_wait() 减1操作 -> P(1)
    • sem_post() 加1操作 -> V(1)
  1. 利用线程信号量实现线程互斥
    银行账户例子:

信号灯I(0或1)
I(1):线程信号量初值为1

线程1取款线程2取款
P(1)P(1)
withdraw()withdraw()
V(1)V(1)
  1. 利用线程信号量实现线程同步
    t1(计算)----->Result------>t2(获取结果)

I(0):线程信号量初值为0

t1(计算)t2(获取结果)
-P(1)
计算并将结果放置到Result从Result中获取结果
V(1)-

代码:

/* pthread_cal.c
 * 
 * 一个线程负责计算结果,一个线程负责获取结果
 * 
 * */

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

//这个结构体为一个共享资源,使用时需要加锁保护
typedef struct
{
	int res;
	sem_t sem;
}Result;

//线程函数1:计算并将结果放置Result中
void* set_fn(void* arg)
{
	int i=1, sum=0;
	//计算1~100的累加,得到5050
	for(; i<=100; i++) sum += i;
	
	Result* r = (Result*)arg;
	//将计算结果放置到Result的res中
	r->res = sum;
	
	//V(1)操作
	sem_post(&r->sem);
	
	return (void*)0;
}

//线程函数2:获得结果
void* get_fn(void* arg)
{
	Result *r = (Result*)arg;
	
	//P(1)操作,若get_fn线程先运行,这里wait会把它挂起
	sem_wait(&r->sem);
	
	//获取计算的结果
	int res = r->res;
	printf("0x%lx get sum is %d\\n", pthread_self(), res);
	
	return (void*)0;
}

//主函数
int main(void)
{
	int err;
	pthread_t cal, get;
	
	Result r;
	
	sem_init(&r.sem,0,0);
	
	//启动获取结果的线程
	err=pthread_create(&get, NULL,get_fn, (void*)&r);
	if(err)perror("pthread create error");
	//启动计算结果的线程
	err=pthread_create(&cal, NULL,set_fn, (void*)&r);
	if(err)perror("pthread create error");
	
	pthread_join(cal, NULL);
	pthread_join(get, NULL);
	
	sem_destroy(&r.sem);
	
	
	return 0;
}

输出:

0xb6dbf460 get sum is 5050

以上是关于Linux学习_线程信号量的主要内容,如果未能解决你的问题,请参考以下文章

Linux学习_线程的互斥

Linux学习_线程的概念创建和终止

Linux多线程_(Posix信号量实现环形队列生产者消费者模型)

Linux学习_线程条件变量和状态转移图

Linux学习笔记(15)-信号量

Linux 多线程编程(二)2019-08-10