23-线程信号量
线程信号量
(1)进程信号量与线程信号量
线程的信号量与进程的信号量几乎完全相同,只不过一个是给进程用的,另一个是给线程用的。
使用进程信号量时,我们自己往往还需要二次封装,线程的信号量函数则不需要,直接就可以使用,所以线程的信号量使用起来更加容易,应该说使用难度非常低。
(2)二值信号量和多值信号量
对于线程信号量来说,也分为二值信号量和多值信号量,同样的我们这里只讲二值信号量。
使用二值信号量时,往往用来实现“互斥”和“同步”。
如果想实现互斥的话,更多的还是使用前面讲的互斥锁来实现,因为线程互斥锁提供了更多可自供选择的功能,比如可以设置为“检错锁”、“递归锁”等。
如果你只是想实现简单互斥的话,不管是使用线程互斥锁的“快锁”来实现,还是使用线程信号量来实现,最终所实现的互斥效果都是一样的
(3)信号量的使用步骤
1)定义信号量集合
(a)用于互斥时,集合中只需要一个信号量。
(b)用于同步时,有几个线程需要同步,集合中就需要包含几个信号量
2)初始化集合中的每个信号量
设置初始值,二值信号量的初始值要么是0、要么是1。
(a)如果是用于互斥,基本都是设置为1
(b)如果是用于同步,看具体情况
和进程信号量时的情况是一样的。
3)p、v操作
p操作:信号量值-1
V操作:信号量值+1
4)进程结束时,删除线程信号量集合
(4)线程信号量相关的函数
1)初始化信号量的函数
(a)函数原型
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
· 功能:初始化线程信号量集合中的某个信号量,给它设置一个初始值。
· 返回值
成功返回0,失败返回-1,errno被设置。
注意信号量的错误号不是返回的,而是设置到errno中。
· 参数
- sem:信号量集合中的某个信号量
信号量集合需要我们自己定义
比如:sem_t sem[3],
线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。
sem[0]:第一个信号量
sem[1]:第二个信号量
sem[2]:第三个信号量
sem_init(&sem[0], int pshared, unsigned int value);
线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。
我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid才能实现共享操作。
- pshared:
+ 0:给线程使用
+ !0:可以给进程使用
不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到进程上时,存在一些不稳定的情况。
- value:初始化值
对于二值信号量来说,要么是1,要么是0
· 使用信号量实现互斥,让多个线程向同一文件写“hello” “world\n”时不要出
现hello hello world的情况。
实现互斥时,信号量集合中只需要一个信号量,而且初始值需要被设置为1。
sem_t sem[1]。//定义信号量集合,集合只有一个元素
sem_init(&sem[0], 0, 1);
for(i=0;i<1;i++)
{
if(i == 0) sem_init(&sem[i], 0, 1);
else sem_init(&sem[i], 0, 0);
}
3)P操作
(a)函数原型
#include <semaphore.h>
int sem_wait(sem_t *sem);//阻塞p操作
· 功能:阻塞p操作集合中某个信号量,值-1
如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止
· 返回值:成功返回0,失败返回-1,errno被设置。
· 参数:p操作的某个信号量。
比如:sem_wait(&sem[0]);
· sem_wait的兄弟函数
int sem_trywait(sem_t *sem):不阻塞
如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有
p操作成功就是出错返回,不再阻塞
4)v操作
(a)函数原型
#include <semaphore.h>
int sem_post(sem_t *sem);
· 功能:对某个信号量进行v操作,v操作不存在阻塞问题。
v操作成功后,信号量的值会+1
· 返回值:成功返回0,失败返回-1,errno被设置。
(b)代码演示
sem_post(&sem[0]);
2)删除信号量
(a)函数原型
#include <semaphore.h>
int sem_destroy(sem_t *sem);
· 功能:删除某个信号量,把所有信号量都删除后,信号量集合就被销毁。
这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合
即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被
删除完毕
(b)代码pth_sem_mutex.c
#include <stdlib.h> #include <pthread.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <semaphore.h> #include <unistd.h> #define SECON_PTH_NUMS 2 #define PTHEXIT -1 #define SEM_NUMS 1 //信号量数量 typedef struct pthread_arg{ pthread_t tid; int pthno; int fd; }ptharg; //全局变量定义后,默认初始化为0 struct global_va{ ptharg pth_arg[SECON_PTH_NUMS]; //结构体数组,每个元素会被当做参数传递给对应的次线程 sem_t sem[1]; } glbva; void print_err(char *str,int line,int err_no){ printf("%d,%s:%s\n",line,str,strerror(err_no) ); exit(-1); } void signal_fun(int signo){ int ret =0; ret = sem_destroy(&glbva.sem[0]); if(ret != 0) print_err("sem_destory err",__LINE__,errno); exit(0); } void *pth_fun(void *pth_arg){ int fd= ((ptharg *)pth_arg)->fd; while(1){ sem_wait(&glbva.sem[0]); write(fd,"php",3); write(fd,"java\n",5); sem_post(&glbva.sem[0]); } return NULL; } int main(int argc, char const *argv[]) { int i=0; int ret =0; int fd =0; signal(SIGINT,signal_fun); fd=open("./file",O_RDWR|O_CREAT|O_TRUNC,0664); if(fd ==-1) print_err("open file fail",__LINE__,errno); //初始化信号量 ret = sem_init(&glbva.sem[0],0,1); if(ret != 0) print_err("open sem_init err",__LINE__,errno); for(i=0;i<SECON_PTH_NUMS;i++){ glbva.pth_arg[i].fd =fd; glbva.pth_arg[i].pthno=i; ret= pthread_create(&glbva.pth_arg[i].tid,NULL,pth_fun,(void *)&glbva.pth_arg[i]); if(ret !=0 ) print_err("pthread_creat err",__LINE__,ret); } while(1){ sem_wait(&glbva.sem[0]); write(fd,"js",2); write(fd,"css\n",4); sem_post(&glbva.sem[0]); } return 0; }
(5)使用线程信号量,实现线程之间的同步
比如有三个线程(1主线程,2个次线程),分别打印333333、222222、111111,使用同步让他们顺序
的打印111111、222222、333333。
· 使用信号量实现同步
使用进程信号量实现进程同步时,有多少个进程需要同步,集合中就需要包含几个信号量。
同样的,使用线程信号量实现同步时,有几个线程需要同步,集合中就需要包含几个信号量
pth_sem_sync.c
#include <stdlib.h> #include <pthread.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <semaphore.h> #include <unistd.h> #define SECON_PTH_NUMS 2 #define PTHEXIT -1 #define SEM_NUMS SECON_PTH_NUMS+1 //信号量数量,主线程占用一个 typedef struct pthread_arg{ pthread_t tid; int pthno; int fd; }ptharg; //全局变量定义后,默认初始化为0 struct global_va{ ptharg pth_arg[SECON_PTH_NUMS]; //结构体数组,每个元素会被当做参数传递给对应的次线程 sem_t sem[SEM_NUMS]; } glbva; void print_err(char *str,int line,int err_no){ printf("%d,%s:%s\n",line,str,strerror(err_no) ); exit(-1); } void signal_fun(int signo){ int i= 0; int ret =0; for (i=0;i<SEM_NUMS;i++){ ret = sem_destroy(&glbva.sem[i]); if(ret != 0) print_err("sem_destory err",__LINE__,errno); } exit(0); } void *pth_fun1(void *pth_arg){ while(1){ sem_wait(&glbva.sem[0]); printf("111111\n"); sleep(1); sem_post(&glbva.sem[1]); } return NULL; } void *pth_fun2(void *pth_arg){ while(1){ sem_wait(&glbva.sem[1]); printf("222222\n"); sleep(1); sem_post(&glbva.sem[2]); } return NULL; } int main(int argc, char const *argv[]) { int i=0; int ret =0; void *(*pth_fun_buf[])(void *)={pth_fun1,pth_fun2}; signal(SIGINT,signal_fun); #if 1 //初始化信号量 for(i=0;i<SEM_NUMS;i++){ if(i == 0) ret = sem_init(&glbva.sem[i],0,1); else ret = sem_init(&glbva.sem[i],0,0); if(ret != 0) print_err("open sem_init err",__LINE__,errno); } #endif for(i=0;i<SECON_PTH_NUMS;i++){ ret= pthread_create(&glbva.pth_arg[i].tid,NULL,pth_fun_buf[i],NULL); if(ret !=0 ) print_err("pthread_creat err",__LINE__,ret); } while(1){ sem_wait(&glbva.sem[2]); printf("33333\n"); sleep(1); sem_post(&glbva.sem[0]); } return 0; }