四十三、Linux 线程——线程同步之线程信号量

时间:2023-03-09 02:14:17
四十三、Linux 线程——线程同步之线程信号量

43.1 信号量

43.1.1 信号量介绍

  • 信号量从本质上是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问
  • 信号量可以实现线程的同步和互斥
  • 通过 sem_post() 和 sem_wait() 函数对信号量进行加减操作从而解决线程的同步和互斥
  • 信号量数据类型:sem_t

43.1.2 信号量创建和销毁

 #include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned value);
int sem_destroy(sem_t *sem);
  • 函数参数:
    • sem:信号量指针
    • pshared:是否在进程间共享的标识,0 为不共享,1 为共享
    • value:信号量的初始值
  • 返回值:成功,返回 0;出错,返回错误编号

43.1.3 信号量的加和减操作

 #include <semaphore.h>
/** 增加信号量的值 */
int sem_post(sem_t *sem); /** 非阻塞版本,减少信号量的值 */
int sem_wait(sem_t *sem); /** 阻塞版本,减少信号量的值 */
int sem_trywait(sem_t *sem);
  • 函数返回值:成功返回0;出错返回错误编号
  • 函数说明:
    • 调用 sem_post() 一次信号量作 +1 操作
    • 调用 sem_wait() 一次信号量作 -1 操作
    • 当线程调用 sem_wait() 后,若信号量的值小于 0 ,则线程阻塞。只有其他线程在调用 sem_post() 对信号量作加操作后,并且其值大于或等于 0 时,阻塞的线程才能继续运行

43.2 例子

43.2.1 例子1

 #include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h> /** 定义线程信号梁 */
sem_t sem1;
sem_t sem2; void *a_fn(void *arg)
{
sem_wait(&sem1);
printf("thread a running\n");
return (void *);
} void *b_fn(void *arg)
{
sem_wait(&sem2);
sem_post(&sem1); ///< 释放线程 a
printf("thread b running\n");
return (void *);
} void *c_fn(void *arg)
{
sem_post(&sem2); ///<释放线程 b,对线程信号量 sem2 做 +1 操作,让阻塞的线程 b 运行
printf("thread c running\n");
return (void *);
} int main(void)
{
pthread_t a, b ,c; /** 线程信号量初始化 */
sem_init(&sem1, , );
sem_init(&sem2, , ); pthread_create(&a, NULL, a_fn, (void *));
pthread_create(&b, NULL, b_fn, (void *));
pthread_create(&c, NULL, c_fn, (void *)); pthread_join(a, NULL);
pthread_join(b, NULL);
pthread_join(c, NULL); sem_destroy(&sem1);
sem_destroy(&sem2); return ;
}

  编译运行结果:

  四十三、Linux 线程——线程同步之线程信号量

43.2.2 PV操作银行账户

    • P 操作:减,如减 1 操作  sem_wait()
    • V 操作:加,加 1 操作 sem_post()

  四十三、Linux 线程——线程同步之线程信号量

  atm_count.h

 #ifndef __ATM_ACCOUNT_H__
#define __ATM_ACCOUNT_H__ #include <math.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h> /** 账户信息 */
typedef struct {
int code; ///< 银行账户的编码
double balance; ///< 账户余额 /** 建议互斥锁用来锁定一个账户(共享资源),和账户绑定再一起,
* 尽量不设置成全局变量,否则可能出现一把锁去锁几百个账户,导致并发性能降低 */
//pthread_mutex_t mutex; ///< 定义一把互斥锁,用来对多线程操作的银行账户(共享资源)进行加锁 //pthread_rwlock_t rwlock; ///<定义读写锁 //定义线程信号量
25 sem_t sem;
}atm_Account; /** 创建账户 */
extern atm_Account *atm_account_Create(int code, double balance);
/** 销毁账户 */
extern void atm_account_Destroy(atm_Account *account);
/** 取款 */
extern double atm_account_Withdraw(atm_Account *account, double amt);
/** 存款 */
extern double atm_account_Desposit(atm_Account *account, double amt);
/** 查看账户余额 */
extern double atm_account_BalanceGet(atm_Account *account); #endif

  atm_account.c

 #include "atm_account.h"

 /** 创建账户 */
atm_Account *atm_account_Create(int code, double balance)
{
atm_Account *account = (atm_Account *)malloc(sizeof(atm_Account));
if(NULL == account) {
return NULL;
} account->code = code;
account->balance = balance; /** 对互斥锁进行初始化 */
//pthread_mutex_init(&account->mutex, NULL); /** 初始化读写锁 */
//pthread_rwlock_init(&account->rwlock, NULL); /** 初始化线程信号量 */
21 sem_init(&account->sem, 0, 1);

return account;
} /** 销毁账户 */
void atm_account_Destroy(atm_Account *account)
{
if(NULL == account){
return ;
} //pthread_mutex_destroy(&account->mutex);
//pthread_rwlock_destroy(&account->rwlock); ///< 销毁读写锁 /** 销毁线程信号量 */
37 sem_destroy(&account->sem);
free(account);
} /** 取款: 成功,则返回取款金额 */
double atm_account_Withdraw(atm_Account *account, double amt)
{
if(NULL == account) {
return 0.0;
} /** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
//pthread_rwlock_wrlock(&account->rwlock); ///< 加写锁
/** P(1) 操作 */
52 sem_wait(&account->sem);

if(amt < || amt > account->balance) {
//pthread_mutex_unlock(&account->mutex);
//pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁 /** V(1) 操作 */
59 sem_post(&account->sem);
return 0.0;
} double balance_tmp = account->balance;
sleep();
balance_tmp -= amt;
account->balance = balance_tmp; //pthread_mutex_unlock(&account->mutex);
//pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁 /** V(1) 操作 */
72 sem_post(&account->sem);
return amt;
} /** 存款: 返回存款的金额 */
double atm_account_Desposit(atm_Account *account, double amt)
{
if(NULL == account){
return 0.0;
} /** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
//pthread_rwlock_wrlock(&account->rwlock); ///< 加写锁 /** P(1) 操作 */
88 sem_wait(&account->sem);

if(amt < ){
//pthread_mutex_unlock(&account->mutex);
//pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁 /** V(1) 操作 */
95 sem_post(&account->sem);
return 0.0;
} double balance_tmp = account->balance;
sleep();
balance_tmp += amt;
account->balance = balance_tmp; //pthread_mutex_unlock(&account->mutex);
//pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁 /** V(1) 操作 */
108 sem_post(&account->sem);
return amt;
} /** 查看账户余额 */
double atm_account_BalanceGet(atm_Account *account)
{
if(NULL == account){
return 0.0;
} /** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
//pthread_rwlock_rdlock(&account->rwlock); ///< 上读锁 /** P(1) 操作 */
124 sem_wait(&account->sem);

double balance_tmp = account->balance;
//pthread_mutex_unlock(&account->mutex);
//pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁 /** V(1) 操作 */
131 sem_post(&account->sem);

return balance_tmp;
}

  编译运行结果:

  四十三、Linux 线程——线程同步之线程信号量

43.2.3 利用线程信号量实现线程之间的同步

  四十三、Linux 线程——线程同步之线程信号量

  

 #include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h> /** 两个线程定义的共享资源 */
typedef struct {
int res;
sem_t sem;
}Result; /** 计算并将结果放置在 Result 中的线程运行函数 */
void *set_fn(void *arg)
{
Result *r = (Result *)arg;
int i = ;
int sum = ; for(; i <= ; i++){
sum += i;
} /** 将结果放置到 Result 中 */
r->res = sum; sem_post(&r->sem);
return (void *);
} /** 获得结果的线程运行函数 */
void *get_fn(void *arg)
{
Result *r = (Result *)arg; sem_wait(&r->sem);
/** 去获取计算结果 */
int res = r->res;
printf("0x%lx get sum is %d\n", pthread_self(), res); return (void *);
} int main(void)
{
int err;
pthread_t cal, get; Result r;
sem_init(&r.sem, , );
/** 启动获取结果的线程 */
if((err = pthread_create(&get, NULL, get_fn, (void *)&r)) != ){
perror("pthread create error");
} /** 启动计算结果的线程 */
if((err = pthread_create(&cal, NULL, set_fn, (void *)&r)) != ){
perror("pthread create error");
} pthread_join(cal, NULL);
pthread_join(get, NULL);
sem_destroy(&r.sem);
return ;
}

  编译运行:

  四十三、Linux 线程——线程同步之线程信号量