目录
一、线程的属性----分离属性(attr->attribute)
2.如何创建分离属性的线程? -> pthread_attr_setdetachstate() -> man 3
方法一:添加一个分离属性到一个属性变量中,然后使用属性变量去创建一个线程,那么创建出来的线程就是具有分离属性的线程。
练习1: 验证一个分离属性的线程退出,主线程还可不可以去接合(阻塞等待回收)它? --不可以
练习3: 使用pthread_detach之后来验证pthread_join阻塞失败?(注意分离属性时间生效的问题)
练习4:验证设置了分离属性的线程,在进程中最多能创建多少条??? --没有限制
1.一般主线程不用于去处理任务,只是控制子线程状态,例如取消,接合.. -> pthread_cancel() -> man 3 pthread_cancel
2.设置线程响应取消的状态。 -> pthread_setcancelstate() -> man 3 pthread_setcancelstate
3.设置线程响应取消的类型。 -> pthread_setcanceltype() -> man 3 pthread_setcanceltype
1)压栈线程的取消例程函数-> pthread_cleanup_push()
2)弹栈线程的取消例程函数 --> pthread_cleanup_pop()
练习5: 子线程收到主线程给自己发送的取消请求,不要马上取消,而是先打印一句话"I recv cancel!\n",再响应取消。顺便使用这个练习,验证以上这3种情况。
4)关闭有名信号量。 -> sem_close() -> man 3 sem_close
5)删除有名信号量。 -> sem_unlink() -> man 3 sem_unlink
2)初始化无名信号量。 -> sem_init() -> man 3 sem_init
5)销毁无名信号量。 -> sem_destroy() -> man 3 sem_destroy
首先分离属性是线程的一个属性,有了分离属性的线程,不需要别的线程去接合自己的(回收自己的资源)。但是虽然说是分离的,但是进程退出了,该线程还是会退出的。
设置了分离属性的线程 -> 不需要pthread_join()
设置了非分离属性的线程 -> 需要pthread_join() -> 默认创建的普通属性线程就是非分离线程。
pthread_attr_setdetachstate
1)定义一个属性变量 -> 数据类型:pthread_attr_t
pthread_attr_t attr;
2)初始化属性变量。 -> pthread_attr_init() -> man 3 pthread_attr_init
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
参数:
attr:未初始化的属性变量
返回值:
成功:0
失败:非0错误码
3)设置分离属性到属性变量中。
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数:
attr:已经初始化过的属性变量
detachstate:
PTHREAD_CREATE_DETACHED -> 分离属性
PTHREAD_CREATE_JOINABLE -> 非分离属性(默认状态)
返回值:
成功:0
失败:非0错误码
4)使用属性变量去创建一个新的线程。
pthread_create(&tid,&attr,.....); -> 创建出来的线程就是分离属性的线程,不需要pthread_join()
5)销毁属性变量。 -> pthread_attr_destroy() -> man 3 pthread_attr_destroy
int pthread_attr_destroy(pthread_attr_t *attr);
参数:
attr:已经初始化过的属性变量
返回值:
成功:0
失败:非0错误码
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1 : 如何创建具有分离属性的线程
void* start_routine(void *arg)
{
printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
sleep(3);
}
int main()
{
//1、定义一个线程属性变量
pthread_attr_t attr;
//2、初始化清空属性变量
pthread_attr_init(&attr);
//3、把分离属性加入到属性变量中
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//4、创建一条具有分离属性的子线程
pthread_t thread;
pthread_create(&thread,&attr,start_routine,NULL);
//5、销毁属性变量
pthread_attr_destroy(&attr);
pause();//暂停自己
}
#include<stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1 : 如何创建具有分离属性的线程
void* start_routine(void *arg)
{
int cnt = 10;
while(cnt--)
{
printf("[%s] is job.... [%d]\n",__FUNCTION__,cnt); //pthread_self打印当前线程的ID号
sleep(1);
}
}
int main()
{
//1、定义一个线程属性变量
pthread_attr_t attr; //不要写成pthread_attr_t *attr;
//2、初始化清空属性变量
pthread_attr_init(&attr);
//3、把分离属性加入到属性变量中
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);//线程的默认状态非分离属性
//4、创建一条具有分离属性的子线程
int ret;
pthread_t tid;
ret = pthread_create(&tid,&attr,start_routine,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
int cnt = 0;
while(cnt--)
{
printf("[%s] is job....\n",__FUNCTION__); //pthread_self打印当前线程的ID号
sleep(1);
}
ret = pthread_join(tid,NULL);
if(ret != 0)
{
perror("pthread_join fail");
return -1;
}
//5、销毁属性变量
pthread_attr_destroy(&attr);
printf("main is over\n");
return 0;
}
/*
int *x,*y;
get xy(int *x,int *y)
{
*x = 10;
*y = 20;
}
get_xy(x,y);//x,y值没有改变
int x,y;
get_xy(int *x,int *y);
{
*x = 10;
*y = 20;
}
get_xy(&x,&y);//x,y值有改变
*/
练习2: 验证一个分离属性的线程,在进程退出时,该分离属性的线程还会不会继续运行? --不会运行了
#include<stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1 : 如何创建具有分离属性的线程
void* start_routine(void *arg)
{
int cnt = 10;
while(cnt--)
{
printf("[%s] is job.... [%d]\n",__FUNCTION__,cnt); //pthread_self打印当前线程的ID号
sleep(1);
}
}
int main()
{
//1、定义一个线程属性变量
pthread_attr_t attr; //不要写成pthread_attr_t *attr;
//2、初始化清空属性变量
pthread_attr_init(&attr);
//3、把分离属性加入到属性变量中
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);//线程的默认状态非分离属性
//4、创建一条具有分离属性的子线程
int ret;
pthread_t tid;
ret = pthread_create(&tid,&attr,start_routine,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
int cnt = 5;
while(cnt--)
{
printf("[%s] is job....[%d]\n",__FUNCTION__,cnt); //pthread_self打印当前线程的ID号
sleep(1);
}
//添加了分离属性的子线程,主线程不需要调用pthread_join去结合它
//5、销毁属性变量
pthread_attr_destroy(&attr);
printf("main is over\n");
return 0;
}
/*
int *x,*y;
get xy(int *x,int *y)
{
*x = 10;
*y = 20;
}
get_xy(x,y);//x,y值没有改变
int x,y;
get_xy(int *x,int *y);
{
*x = 10;
*y = 20;
}
get_xy(&x,&y);//x,y值有改变
*/
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//方法1 : 如何创建具有分离属性的线程
/*
练习1: 验证一个分离属性的线程退出,主线程还可不可以去接合(阻塞等待回收)它?
练习2: 验证一个分离属性的线程,在进程退出时,该分离属性的线程还会不会继续运行?
*/
void* start_routine(void *arg)
{
printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
sleep(3);
}
int main()
{
//1、定义一个 线程属性变量
pthread_attr_t attr;
//2、初始化清空 属性变量
pthread_attr_init(&attr);
//3、把分离属性 加入 到 属性变量中
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//4、创建一条具有分离属性的子线程
pthread_t thread;
pthread_create(&thread,&attr,start_routine,NULL);
//5、销毁属性变量
pthread_attr_destroy(&attr);
//阻塞等待子线程退出,回收资源
int ret = pthread_join(thread,NULL);
if(ret != 0){
printf("pthread_join error\n");
}
}
现象:
pthread_join失败,回收资源失败
方法二:先创建一个普通线程,然后在线程中调用一个设置分离属性的函数,那么这个线程就变成分离的属性。
1)设置线程本身的属性为分离属性。 -> pthread_detach() -> man 3 pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
函数作用:
设置分离属性给线程
参数:
thread:需要设置分离属性的线程的ID号
返回值:
成功:0
失败:非0错误码
2)获取线程的ID号。 -> pthread_self() -> man 3 pthread_self
#include <pthread.h>
pthread_t pthread_self(void);
参数:
无
返回值:线程的ID号。
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1 : 如何创建具有分离属性的线程
void* start_routine(void *arg)
{
//在子线程的内部 设置 分离属性
pthread_detach(pthread_self());
printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
}
int main()
{
//4、创建一条具有分离属性的子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
//pause();
}
现象:
#include<stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1 : 如何创建具有分离属性的线程
void* start_routine(void *arg)
{
pthread_detach(pthread_self()); //写法二
int cnt = 10;
while(cnt--) //子线程想要工作10秒
{
printf("[%s] is job.... [%d]\n",__FUNCTION__,cnt); //pthread_self打印当前线程的ID号
sleep(1);
}
//pthread_exit(NULL);
}
int main()
{
int ret;
pthread_t tid;
ret = pthread_create(&tid,NULL,start_routine,NULL);
if(ret != 0)
{
perror("pthread_create fail");
return -1;
}
//pthread_detach(tid); //写法一
int cnt = 5;
while(cnt--) //主线程工作5秒之后是退出还是等待---退出
{
printf("[%s] is job....[%d]\n",__FUNCTION__,cnt); //pthread_self打印当前线程的ID号
sleep(1);
}
//添加了分离属性之后,pthread_join就不用了
/* ret = pthread_join(tid,NULL);
if(ret != 0)
{
perror("pthread_join fail");
return -1;
} */
printf("main is over\n");
return 0;
}
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
//方法1:如何创建具有分离属性的线程
void* start_routine(void *arg)
{
//在子线程的内部设置分离属性
pthread_detach(pthread_self());
printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
}
int main()
{
//4、创建一条具有分离属性的子线程
pthread_t thread;
int cnt=0;
while(1)
{
int ret= pthread_create(&thread,NULL,start_routine,NULL);
if(ret != 0){
printf("pthread_create error\n");
break;
}
printf("cnt:%d\n",cnt++);
}
}
总结:
无论是否添加了分离属性的线程,理论上创建的线程的数量没有限制。
主线程 -> 取消请求 -> 子线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
函数作用:
发送一个取消请求给子线程。
参数:
thread:需要取消的线程的ID号。
返回值:
成功:0
失败:非0错误码
注意:线程收到取消请求,就等价于提前退出。
pthread_exit() -> 是线程主动退出 ->退出值-> pthread_join()
主线程给子线程发送取消请求--->子线程收到取消请求-> 线程被迫退出(被动退出) -> 没有退出值例题: 尝试使用pthread_cancel去取消一个线程。
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
int pthread_status = 10;
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
int cnt=30;
while(cnt--)
{
sleep(1);
printf("[%lu]start_routine cnt:%d\n",pthread_self(),cnt); //pthread_self 打印自己的线程ID号
}
pthread_exit(&pthread_status);
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
sleep(5);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
void *p = NULL;
pthread_join(thread,&p); // p = (void*)&pthread_status
return 0;
}
现象:
取消线程之后,主线程是无法接受它的返回值
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
参数:
state:
PTHREAD_CANCEL_ENABLE -> 能响应 -> 线程默认属性
-> 是马上响应,还是延迟响应 -> 取决于type。
PTHREAD_CANCEL_DISABLE -> 不能响应
oldstate:保留之前的状态,如果不关心,则填NULL。
返回值:
成功:0
失败:非0错误码。
说明:
pthread_setcancelstate一般使用在子线程中
If a thread has disabled cancellation, then a cancellation request remains queued until the thread enables cancellation.
//如果一个线程不能响应取消的,那么在这个过程中收到了取消请求,那么这个请求会直到这个线程能响应取消请求为止才会被响应。
If a cancellation request is received, it is blocked until cancelability is enabled.
//如果收到取消请求,那么就会阻塞到这个线程能响应为止。
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
int pthread_status = 20;
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
//子线程设置不响应取消请求
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int cnt=0;
while(1)
{
sleep(1);
printf("start_routine:%lu cnt:%d\n",pthread_self(),cnt++); //pthread_self 打印自己的线程ID号
if(cnt == 10)
{
//子线程设置 取消请求能响应
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE , NULL);
}
}
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
sleep(2);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
现象:
刚开始子线程不能接收取消请求,10秒之后才能取消。
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
参数:
type:
PTHREAD_CANCEL_DEFERRED -> 延迟取消 -> 遇到一个取消点函数才会响应取消-> 线程默认属性
PTHREAD_CANCEL_ASYNCHRONOUS -> 立即响应
oldtype:保存之前的状态,如果不关心,则填NULL。
注意: 线程是遇到取消点函数之后才会响应取消的
取消点函数有哪些呢? -> man 7 pthreads
fprintf()
fputc()
fputs()
sleep()
printf()
usleep()
例子1:线程延迟取消,遇到取消点才会取消
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
while(1)
{
//printf("start_routine\n");//取消点函数
//sleep(1); //取消点函数
}
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
现象:主线程给子线程 发送线程取消 ,但是子线程在执行过程中没有遇到取消点函数,所以不能响应取消请求。例子2:给子线程设置立即响应取消请求,收到主线程发来的取消请求,立即响应
#include<stdio.h>
#include<pthread.h>
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
//当线程收到取消请求之后,立即响应,不需要遇到取消点
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
while(1)
{
//printf("start_routine\n");//取消点函数
//sleep(1); //取消点函数
}
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
现象: 子线程收到主线程的取消请求后,立即响应退出线程
当线程收到取消请求时,先不要马上响应取消请求,而是执行一个例程函数先,执行完这个函数再响应取消。
为了防止线程带着一些公共资源而被取消掉,如果带着资源来退出,那么其他线程无法再次使用该资源。
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数:
routine: 线程的取消例程函数 -> 必须是: void fun(void *arg)
arg:传递给取消例程函数的参数
#include <pthread.h>
void pthread_cleanup_pop(int execute);
参数:
execute:
0 -> 删除时,直接删除。
非0 -> 删除时,会先执行一遍例程函数,再删除。
模型:
void* fun(void *arg)
{
pthread_cleanup_push(xxxx); -> 将来收到取消请求,就会执行xxxx这个函数。
.....;
.....; <-- 收到取消请求
.....; pthread_cleanup_pop(0);
}
注意:
>1.子线程收到取消请求之后,(遇到取消点函数)就会执行线程取消例程函数,然后执行完就响应取消请求直接退出,不会再往下面执行了 。
>2.如果子线程没有收到取消请求,而且程序执行到pthread_cleanup_pop该函数时,此函数才会执行,
并且根据参数决定是否执行线程取消例程函数再退出子线程。
>3.这两个函数都必须是成对出现的,如果只写一个直接编译会报错
例子:
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void routine(void *arg)
{
printf("线程取消例程\n");
}
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
//压栈
pthread_cleanup_push(routine,NULL);
while(1)
{
printf("start_routine\n");
sleep(1);
}
//弹栈
pthread_cleanup_pop(0);//0不执行 非0才执行
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
sleep(5);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
现象:子线程收到取消请求之后,立即执行取消例程函数。pop后面的函数不执行。
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
//线程取消例程函数
void routine(void *arg)
{
printf("线程取消例程\n");
}
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
//压栈
pthread_cleanup_push(routine,NULL); //---{
//默认状态-延迟取消,遇到取消点函数再取消
printf("i recv cancel\n");//取消点函数
//弹栈
pthread_cleanup_pop(1);//0不执行 非0才执行 //---}
}
int main()
{
//创建一条子线程
int ret;
pthread_t tid;
ret = pthread_create(&tid,NULL,start_routine,NULL);
if(ret != 0)
{
perror("pthread_create fail");
}
//添加分离属性和压栈弹栈不冲突,如果添加了分离属性就不要用pthread_join结合它
//否则功能会冲突
//pthread_detach(tid);
//给子线程发送一个取消请求
pthread_cancel(tid);
//等待子线程退出
pthread_join(tid,NULL);
return 0;
}
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
//全局变量-->共享变量
int g_val = 0;
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine1(void *arg)
{
g_val = 100;
sleep(2);//延时2秒,此时线程2执行g_val = 200; 该变量的值已经被线程2改变了
printf("start_routine1 g_val:%d\n",g_val);
pthread_exit(NULL);
}
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine2(void *arg)
{
sleep(1);//延时1秒,此时 线程1 执行 g_val = 100;
g_val = 200;
printf("start_routine2 g_val:%d\n",g_val);
pthread_exit(NULL);
}
int main()
{
//创建一条子线程
pthread_t thread1;
pthread_create(&thread1,NULL,start_routine1,NULL);
//创建一条子线程
pthread_t thread2;
pthread_create(&thread2,NULL,start_routine2,NULL);
//等待子线程退出
pthread_join(thread1,NULL);
return 0;
}
现象:
start_routine2 g_val:200
start_routine1 g_val:200
结论:线程1和线程2同时访问某一片内存空间,导致数据发生践踏。
如何去避免呢?? 也就是线程1在访问这片内存空间时,别的线程不能进来。---使用同步互斥机制
(各个线程操作数据的时候没有保护机制,很容易自己的数据被其它的线程篡改,危险)
同步互斥就是使得线程处理任务时有先后的顺序,为了防止线程资源被抢占的问题。
信号量 -> 进程 (共享内存+信号量一起使用) ---进程IPC中的一种
有名信号量 -> 进程 (共享内存+有名信号量一起使用) <编译的时候需要链接线程库-lpthread>
无名信号量 -> 线程
互斥锁 -> 线程
读写锁 -> 线程
条件变量 -> 线程(互斥锁+条件变量一起使用)
有名信号量与信号量非常相似,但是信号量的值只能是0/1,而是有名信号量可以0~正无穷。
信号量使用空间 + 数据来处理互斥,而有名信号量只是使用数据来处理。
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
函数作用:
初始化并且打开一个有名信号量
参数:
name: 有名信号量的名字,要求必须以"/"开头,例如"/sem_test" -> 存在于/dev/shm目录下
oflag:
O_CREAT -> 如果不存在创建
O_CREAT|O_EXCL -> 不存在则创建 -> 存在就报错
mode:八进制权限,例如: 0777
value:有名信号量的起始值
注意:
如果oflag中有O_CREAT这个选项,则这个mode与value必须要填。
如果有名信号量已经存在的了,但是你又写了O_CREAT,那么后面你填的mode和value就会被忽略。
返回值:
成功:有名信号量的地址
失败:SEM_FAILED -> NULL / (sem_t *)-1
P操作: 资源数-1操作 -> sem_wait() -> man 3 sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem); -> 如果减不了1,这个函数就会阻塞
参数:
sem:有名信号量的地址
返回值:
成功:0 -> 资源数可以-1
失败:-1
比如:
如果当前的值为2,那么sem_wait()就会马上返回,并且将值设置为1。
如果当前的值为1,那么sem_wait()就会马上返回,并且将值设置为0。
如果当前的值为0,那么sem_wait()就会阻塞,一直阻塞到有名信号量的值不为0为止。
V操作: 资源数+1操作 -> sem_post() -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
sem:有名信号量的地址
返回值:
成功:0 -> 资源数可以+1
失败:-1
#include <semaphore.h>
int sem_close(sem_t *sem);
参数:
sem:有名信号量的地址
返回值:
成功:0
失败:-1
#include <semaphore.h>
int sem_unlink(const char *name);
参数:
name: 有名信号量的名字
返回值:
成功:0
失败:-1
例子:使用有名信号量+共享内存实现 两个进程的通信。
说明:
《编译有名信号量的时候需要加线程库来编译》
不同点:
1)信号量函数复杂,有名信号量函数简单。
2)信号量有空间和数据两个需要操作;有名信号量只有数据一个需要操作。
3)信号量编译的时候不需要线程库;有名信号量编译的时候需要链接线程库。
相同点:
1)都是作用于进程之间的通信
2)都有P/V操作;P是减1操作,V是加1操作
进程1:
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#define SEM_NAME "/semname1"
int main()
{
//1、获取key值
key_t key = ftok(".",10);
//2、根据key值 获取共享内存的ID号
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
char*shm_p = shmat(shmid,NULL,0);
if(shm_p == (void*)-1)
{
perror("shmat error");
return -1;
}
//4、创建并打开一个有名信号量
sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
if(nameSem == SEM_FAILED )
{
printf("sem_open error\n");
return -1;
}
//此时映射出来的shm_p 就是两个进程的共享内存
while(1)
{
//从键盘上获取数据,存储到共享内存shm_p
scanf("%s",shm_p);
//有名信号量的V操作 数据 +1
sem_post(nameSem);
//退出条件,这里要注意 应该使用strncmp 指定字节数
if(strncmp(shm_p,"exit",4) == 0)
break;
}
//4、当不再使用时,解除映射关系
shmdt(shm_p);
//5、当没有进程再需要使用这块共享内存时,删除它
shmctl(shmid, IPC_RMID, NULL);
//6、关闭有名信号量。
sem_close(nameSem);
//7、删除有名信号量
sem_unlink(SEM_NAME);
return 0;
}
进程2:
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#define SEM_NAME "/semname1"
int main()
{
//1、获取key值
key_t key = ftok(".",10);
//2、根据key值 获取共享内存的ID号
int shmid = shmget(key,1024,IPC_CREAT|0666);
//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
char*shm_p = shmat(shmid,NULL,0);
if(shm_p == (void*)-1)
{
perror("shmat error");
return -1;
}
//4、创建并打开一个有名信号量
sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
if(nameSem == SEM_FAILED )
{
printf("sem_open error\n");
return -1;
}
//此时映射出来的shm_p 就是两个进程的共享内存
while(1)
{
//有名信号量的P操作 数据 -1
sem_wait(nameSem);
//从共享内存中读取数据
printf("recv:%s\n",shm_p);
//退出条件,这里要注意 应该使用strncmp 指定字节数
if(strncmp(shm_p,"exit",4) == 0)
break;
}
return 0;
}
一般作用于线程之间的互斥,由于是无名信号量,所以说是没有名字的,不能使用sem_open打开。
sem_t sem; -> 无名信号量不是一个文件,而是一个变量。
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem: 无名信号量的变量的地址
pshared:
0 -> 作用于线程之间 -> 只考虑这种情况
非0-> 作用于进程之间
value: 无名信号量的起始值。
返回值:
成功:0
失败:-1
P操作: 资源数-1操作 -> sem_wait() -> man 3 sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem); -> 如果减不了1,这个函数就会阻塞
参数:
sem:无名信号量的地址
返回值:
成功:0 -> 资源数可以-1
失败:-1
比如:
如果当前的值为2,那么sem_wait()就会马上返回,并且将值设置为1。
如果当前的值为1,那么sem_wait()就会马上返回,并且将值设置为0。
如果当前的值为0,那么sem_wait()就会阻塞,一直阻塞到有名信号量的值不为0为止。
V操作: 资源数+1操作 -> sem_post() -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
sem:无名信号量的地址
返回值:
成功:0 -> 资源数可以+1
失败:-1
#include <semaphore.h>
int sem_destroy(sem_t *sem);
参数:
sem:无名信号量的地址
返回值:
成功:0
失败:-1例子:线程使用无名信号量实现互斥
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
//1、定义一个无名信号量变量
sem_t g_sem;
int g_val = 0;
//线程1的例程函数
void* start_routine1(void *arg)
{
printf("%s start ...\n",__FUNCTION__);
//在访问 共享资源之前,先看看有没有其他线程正在使用
//无名信号量的P操作
//也就是说,能不能进行 -1 操作 如果能 则往下面 走
//如果不能 ,则阻塞在这里
sem_wait(&g_sem);
g_val = 200;
sleep(2);
printf("start_routine1 g_val:%d\n",g_val);
//当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
sem_post(&g_sem);
printf("%s end ...\n",__FUNCTION__);
}
//线程2的例程函数
void* start_routine2(void *arg)
{
printf("%s start ...\n",__FUNCTION__);
sem_wait(&g_sem);
sleep(1);
g_val = 400;
printf("start_routine2 g_val:%d\n",g_val);
//当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
sem_post(&g_sem);
printf("%s end ...\n",__FUNCTION__);
}
//线程3 的 例程函数
void* start_routine3(void *arg)
{
printf("%s start ...\n",__FUNCTION__);
sem_wait(&g_sem);
sleep(1);
g_val = 600;
printf("start_routine3 g_val:%d\n",g_val);
//当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
sem_post(&g_sem);
printf("%s end ...\n",__FUNCTION__);
}
int main()
{
//2、初始化无名信号量 ---sem_init
sem_init(&g_sem,0, 1);
//创建一条子线程
pthread_t thread1;
pthread_create(&thread1,NULL,start_routine1,NULL);
pthread_t thread2;
pthread_create(&thread2,NULL,start_routine2,NULL);
pthread_t thread3;
pthread_create(&thread3,NULL,start_routine3,NULL);
sem_wait(&g_sem);
g_val = 1000;
printf("main g_val:%d\n",g_val);
//当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
sem_post(&g_sem);
//阻塞等待子线程退出,回收资源
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
pthread_join(thread3,NULL);
//销毁无名信号量
sem_destroy(&g_sem);
}
答案:
练习6:
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
// 练习1: 有一个进程,创建5个线程出来,每一个线程任务都是一样。
// 任务: 将"helloworld"字符串每隔1S打印一个字符。 -> 任务:10S
// 结果: hhhhheeeeellllllllllooooowwwwwooooorrrrrlllllddddd
// 1S 1S 1S 1S 1S 1S 1S 1S 1S 1S
//线程1 的 例程函数
void* start_routine(void *arg)
{
int cnt = 0;
char str[] = "helloworld";
while(1)
{
sleep(1);
fprintf(stderr,"%c",str[cnt++]); //stde
rr 标准出错 ,没有缓冲区
if(cnt == strlen(str))
break;
}
pthread_exit(NULL);
}
int main()
{
//创建5条子线程
pthread_t thread[5];
for(int i=0; i<5; i++){
pthread_create(&(thread[i]),NULL,start_routine,NULL);
}
//阻塞等待子线程退出,回收资源
for(int i=0; i<5; i++){
pthread_join(thread[i],NULL);
}
return 0;
}
练习7:
#include<stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/sem.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#if 0
练习2: 有一个进程,创建5个线程出来,每一个线程任务都是一样。 (使用无名信号量去处理同步互斥) -> 0是不能变成-1的
任务: 将"helloworld"字符串每隔1S打印一个字符。 -> 任务:10S
结果: helloworldhelloworldhelloworldhelloworldhelloworld
10S 10S 10S 10S 10S
#endif
//1、定义一个无名信号量
sem_t g_sem;
void* start_routine(void*arg)
{
//进行P 操作 -1
sem_wait(&g_sem);
char str[ ] = "helloworld";
for(int i=0;i<strlen(str);i++)
{
sleep(1);
fprintf(stderr,"%c",str[i]);
}
//进行V操作 + 1
sem_post(&g_sem);
pthread_exit(0);
}
int main()
{
//2、初始化无名信号量
sem_init(&g_sem,0,1);
//创建5条子线程
pthread_t thread[5];
for(int i=0; i<5; i++){
pthread_create(&(thread[i]),NULL,start_routine,NULL);
}
//阻塞等待子线程退出,回收资源
for(int i=0; i<5; i++){
pthread_join(thread[i],NULL);
}
sem_destroy(&g_sem);
return 0;
}