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 ", 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 ", 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 ", 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) ", 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) ", 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 ", 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 ", 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 */