第9章 线程编程(7)_线程同步4:信号量

5.5 线程信号量

5.5.1 信号量函数简介

(1)信号量(semaphore)从本质上是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问。

(2)信号量可以实现线程的同步和互斥

(3)通过sem_post()sem_wait()函数对信号量进行加减操作,从而解决线程的同步和互斥。

(4)信号量数据类型sem_t

5.5.2 信号量的操作

(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:信号量的初始值

(2)信号量的加和减操作

头文件

#include <semaphore.h>

函数

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

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

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

返回值

成功返回0,否则返回错误编号

备注

①调用sem_post一次,信号量作加1操作。

②调用sem_wait一次,信号量作减1操作。

③当线程调用sem_wait后,若信号量的值小于0,则线程阻塞只有其它线程在调用sem_post对信号量作加操作后并且其值大小或等于0时,阻塞的线程才能继续运行

【编程实验】三个线程交替输出

//sem_test.c

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

//三个线程按a、b、c的顺序交替输出

//定义线程信号量
sem_t semA;
sem_t semB;
sem_t semC;

#define LOOP 5 //循环次序

void* a_fn(void* arg)
{
    int i=0;
    for(; i<LOOP; i++){
        sem_wait(&semA);
        printf("%d thread a(0x%lx) is running\n", i+1, pthread_self());
        sem_post(&semB); //唤醒b线程
    }

    return (void*)0;
}

void* b_fn(void* arg)
{
    int i=0;
    for(; i<LOOP; i++){
        sem_wait(&semB);
        printf("%d thread b(0x%lx) is running\n", i+1, pthread_self());
        sem_post(&semC); //唤醒c线程
    }

    return (void*)0;
}

void* c_fn(void* arg)
{
    int i=0;
    for(; i<LOOP; i++){
        sem_wait(&semC);
        printf("%d thread c(0x%lx) is running\n", i+1, pthread_self());
        sem_post(&semA); //唤醒a线程
    }

    return (void*)0;
}

int main(void)
{
    pthread_t a, b, c;

    //初始化线程信号量
    sem_init(&semA, 0, 1); //信号灯为1,表示a先运行
    sem_init(&semB, 0, 0); //b、c线程先暂停
    sem_init(&semC, 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(&semA);
    sem_destroy(&semB);
    sem_destroy(&semC);

    return 0;
}
/*输出结果:
1 thread a(0xb7779b70) is running
1 thread b(0xb6d78b70) is running
1 thread c(0xb6377b70) is running
2 thread a(0xb7779b70) is running
2 thread b(0xb6d78b70) is running
2 thread c(0xb6377b70) is running
3 thread a(0xb7779b70) is running
3 thread b(0xb6d78b70) is running
3 thread c(0xb6377b70) is running
4 thread a(0xb7779b70) is running
4 thread b(0xb6d78b70) is running
4 thread c(0xb6377b70) is running
5 thread a(0xb7779b70) is running
5 thread b(0xb6d78b70) is running
5 thread c(0xb6377b70) is running
*/

【编程实验】PV操作:银行帐户

(1)PV原语操作:P操作——减;V操作:加;

(2)sem_post 信号灯加1操作 ==>V(1)操作;

     sem_wait 信号灯减1操作 ==>P(1)操作;

//account.h

#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
#include <pthread.h>
#include <semaphore.h>

typedef struct
{
    int      code;    //帐号
    double   balance; //余额

    //使用信号量,用来对多线程操作的银行帐户(共享资源)进行加锁保护。
    /*
     *建议信号量和一个帐户绑定。尽量不设置成全局变量,否则可能出更一
     *锁去锁定多个帐户,导致并发性能降低。
     */
    sem_t sem; //定义线程信号量
}Account;

//创建账户
extern Account* create_account(int code, double balance);
//销毁帐户
extern void destroy_account(Account* a);
//取款
extern double withdraw(Account* a, double amt); //amt == amount
//存款
extern double deposit(Account* a, double amt);
//查看帐户余额
extern double get_balance(Account* a);

#endif  //__ACCOUNT_H__

//account.c

#include "account.h"
#include <malloc.h>
#include <string.h>
#include <assert.h>

//创建账户
Account* create_account(int code, double balance)
{
    Account* ret = (Account*)malloc(sizeof(Account));
    assert(ret != NULL);

    ret->code = code;
    ret->balance = balance;

    //对信号量进行初始化
    sem_init(&ret->sem, 0, 1);//1个信号灯
    return ret;
}

//销毁帐户
void destroy_account(Account* a)
{
    assert( a != NULL);

    //销毁信号量
    sem_destroy(&a->sem);

    free(a);
}

//取款
double withdraw(Account* a, double amt) //amt == amount
{
    assert(a != NULL);

    //等待信号量:P(1)操作
    sem_wait(&a->sem);

    if((amt < 0) || (amt > a->balance)){
        //V(1)操作
        sem_post(&a->sem);
        return 0.0;
    }

    double balance = a->balance; //先取余额

    sleep(1); //为模拟多线程下可能出现的问题

    balance -= amt;
    a->balance = balance; //更新余额。在读取余额和更新余额之间有
                          //故意留出“时间窗口”。

    //V(1)操作
    sem_post(&a->sem);

    return amt;    
}

//存款
double deposit(Account* a, double amt)
{
    assert(a != NULL);

    if(amt < 0){
        return 0.0;
    }

    //P(1)操作
    sem_wait(&a->sem);//等待信号量

    double balance = a->balance; //先取余额

    sleep(1); //为模拟多线程下可能出现的问题

    balance += amt;
    a->balance = balance; //更新余额。

    //V(1)操作
    sem_post(&a->sem); //通知操作完毕

    return amt;    
}

//查看帐户余额
double get_balance(Account* a)
{
    assert(a != NULL);

    //P(1)操作
    sem_wait(&a->sem);
    double balance = a->balance;
    //V(1)操作
    sem_post(&a->sem);

    return balance;
}

//account_test.c

#include "account.h"
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//#include <string.h> //for strcpy

typedef struct
{
    char name[20];
    Account* account;
    double amt;
}OperArg;

//定义取款操作的线程函数
void* withdraw_fn(void* arg)
{
    OperArg* oa = (OperArg*)arg;

    double amt = withdraw(oa->account, oa->amt);
    printf("%s(0x%lx) withdraw %f from account(%d)\n", 
              oa->name,pthread_self(), amt, oa->account->code);

    return (void*)0;
}

//定义存款操作的线程函数
void* deposit_fn(void* arg)
{
    OperArg* oa = (OperArg*)arg;

    double amt = deposit(oa->account, oa->amt);
    printf("%s(0x%lx) deposit %f from account(%d)\n", 
              oa->name,pthread_self(), amt, oa->account->code);

    return (void*)0;
}

int main(void)
{
    int err = 0;
    pthread_t boy, girl;

    Account* a = create_account(100001, 10000);
    OperArg o1 = {"boy",  a, 10000}; //strcpy(o1.name, "boy");
    OperArg o2 = {"girl", a, 10000};

    //启动两个子线程(boy和girl线程)同时去操作同一个银行帐户
    if((err = pthread_create(&boy, NULL, withdraw_fn, (void*)&o1)) != 0){
        perror("pthread_create error");
    }

    if((err = pthread_create(&girl, NULL, withdraw_fn, (void*)&o2)) != 0){
        perror("pthread_create error");
    }

    pthread_join(boy, NULL);
    pthread_join(girl, NULL);

    //查看余额
    printf("account balance: %f\n", get_balance(a));

    destroy_account(a);

    return 0;
}

【编程实验】PV操作:计算1+2+3+…+100

//pthread_sem_cal.c

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

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

typedef struct
{
    int      res; //计算结果
    sem_t    sem;
}Result;

//计算并将结果放置在Result中的线程函数
void*  set_fn(void* arg)
{
    Result* r = (Result*)arg;

    int i = 1;
    int sum = 0;
    for(; i<=100; i++){
        sum += i;
    }

    //将计算结果放入Result中
    r->res = sum;

    //通知获取结果的线程,计算完毕!
    sem_post(&r->sem);

    return (void*)0;
}

//获取结果的线程运行函数
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*)0;
}

int main(void)
{
    int err = 0;
    pthread_t  cal, get;
    Result  r;
    sem_init(&r.sem, 0, 0); //信号灯初始为0,让读取线程进入等待状态

    //启动获取结果的线程
    if((err = pthread_create(&get, NULL, get_fn, (void*)&r)) !=0 ){
        perror("pthread create error");
    }

    //启动计算的线程
    if((err = pthread_create(&cal, NULL, set_fn, (void*)&r)) !=0 ){
        perror("pthread create error");
    }

    //等待子线程结束
    pthread_join(cal, NULL);
    pthread_join(get, NULL);

    //销毁信号量
    sem_destroy(&r.sem);

    return 0;
}
/*输出结果
 0xb6df1b70 get sum is 5050
 */

原文链接: https://www.cnblogs.com/5iedu/p/6424376.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第9章 线程编程(7)_线程同步4:信号量

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/393597

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午3:16
下一篇 2023年4月3日 下午3:16

相关推荐