当前位置: 首页 > 工具软件 > until-destroy > 使用案例 >

系统编程06-(pthread_attr_init、pthread_attr_setdetachstate、pthread_attr_destroy、pthread_detach......)

拓拔辰钊
2023-12-01

目录

一、线程的属性----分离属性(attr->attribute)

1.什么是分离属性?

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.什么是线程取消例程函数

2.为什么要使用取消例程函数

3.如何实现

1)压栈线程的取消例程函数-> pthread_cleanup_push()

2)弹栈线程的取消例程函数  --> pthread_cleanup_pop()

练习5:  子线程收到主线程给自己发送的取消请求,不要马上取消,而是先打印一句话"I recv cancel!\n",再响应取消。顺便使用这个练习,验证以上这3种情况。

四、多条线程同时访问某一片内存空间导致数据践踏

五、线程同步互斥方式

1、什么是同步互斥?为什么要处理同步互斥?

2、处理同步互斥方式有哪些?

六、同步互斥方式 -- 有名信号量

1.有名信号量的特点

2.有名信号量的函数接口

1)创建并打开一个有名信号量  -> sem_open()

2)有名信号量的P操作

3)有名信号量的V操作

4)关闭有名信号量。   -> sem_close()  -> man 3 sem_close

3.有名信号量与信号量的比较

七、同步互斥方式  --- 无名信号量

1.什么是无名信号量?

2.无名信号量 函数接口

1)定义一个无名信号量  (数据类型: sem_t)

2)初始化无名信号量。 -> sem_init()  -> man 3 sem_init

3)无名信号量的P操作

4)无名信号量的V操作

5)销毁无名信号量。 -> sem_destroy()  -> man 3 sem_destroy

练习6: 有一个进程,创建5个线程出来,每一个线程任务都是一样。任务: 将"helloworld"字符串每隔1S打印一个字符。   -> 任务:10S结果: hhhhheeeeellllllllllooooowwwwwooooorrrrrlllllddddd    1S   1S   1S  1S    1S   1S  1S   1S    1S  1S

练习7: 有一个进程,创建5个线程出来,每一个线程任务都是一样。  (使用无名信号量去处理同步互斥)  -> 0是不能变成-1的任务: 将"helloworld"字符串每隔1S打印一个字符。   -> 任务:10S结果: helloworldhelloworldhelloworldhelloworldhelloworld10S       10S      10S        10S       10S


一、线程的属性----分离属性(attr->attribute)

1.什么是分离属性?

首先分离属性是线程的一个属性,有了分离属性的线程,不需要别的线程去接合自己的(回收自己的资源)。但是虽然说是分离的,但是进程退出了,该线程还是会退出的。

设置了分离属性的线程  -> 不需要pthread_join()
设置了非分离属性的线程  -> 需要pthread_join()   -> 默认创建的普通属性线程就是非分离线程。

2.如何创建分离属性的线程? -> pthread_attr_setdetachstate()  -> man 3

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();//暂停自己
}

练习1: 验证一个分离属性的线程退出,主线程还可不可以去接合(阻塞等待回收)它?  --不可以

#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();
}
现象:

练习3:
    使用pthread_detach之后来验证pthread_join阻塞失败?(注意分离属性时间生效的问题)

#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;
}


练习4:验证设置了分离属性的线程,在进程中最多能创建多少条???  --没有限制

#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++);
    }
}

总结:
 无论是否添加了分离属性的线程,理论上创建的线程的数量没有限制。

二、线程的取消(杀死一个子线程)

1.一般主线程不用于去处理任务,只是控制子线程状态,例如取消,接合..   -> pthread_cancel() -> man 3 pthread_cancel

主线程  -> 取消请求  -> 子线程
#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;
}

现象:
    取消线程之后,主线程是无法接受它的返回值


2.设置线程响应取消的状态。  -> pthread_setcancelstate()  -> man 3 pthread_setcancelstate

#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秒之后才能取消。

3.设置线程响应取消的类型。  -> pthread_setcanceltype()  -> man 3 pthread_setcanceltype

#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;
}

现象: 子线程收到主线程的取消请求后,立即响应退出线程

三、线程取消例程函数

1.什么是线程取消例程函数

当线程收到取消请求时,先不要马上响应取消请求,而是执行一个例程函数先,执行完这个函数再响应取消。


2.为什么要使用取消例程函数

为了防止线程带着一些公共资源而被取消掉,如果带着资源来退出,那么其他线程无法再次使用该资源。


3.如何实现

1)压栈线程的取消例程函数-> pthread_cleanup_push()

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数:
    routine: 线程的取消例程函数   -> 必须是: void fun(void *arg)
    arg:传递给取消例程函数的参数

2)弹栈线程的取消例程函数  --> pthread_cleanup_pop()

#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后面的函数不执行。

练习5: 
 子线程收到主线程给自己发送的取消请求,不要马上取消,而是先打印一句话"I recv cancel!\n",再响应取消。
顺便使用这个练习,验证以上这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);  //---{
    
	//默认状态-延迟取消,遇到取消点函数再取消
    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在访问这片内存空间时,别的线程不能进来。---使用同步互斥机制
(各个线程操作数据的时候没有保护机制,很容易自己的数据被其它的线程篡改,危险)

五、线程同步互斥方式

1、什么是同步互斥?为什么要处理同步互斥?

同步互斥就是使得线程处理任务时有先后的顺序,为了防止线程资源被抢占的问题。


2、处理同步互斥方式有哪些?

信号量      -> 进程  (共享内存+信号量一起使用) ---进程IPC中的一种
有名信号量  -> 进程     (共享内存+有名信号量一起使用) <编译的时候需要链接线程库-lpthread>
无名信号量  -> 线程
互斥锁      -> 线程
读写锁      -> 线程
条件变量    -> 线程(互斥锁+条件变量一起使用)

六、同步互斥方式 -- 有名信号量

1.有名信号量的特点

有名信号量与信号量非常相似,但是信号量的值只能是0/1,而是有名信号量可以0~正无穷。
信号量使用空间 + 数据来处理互斥,而有名信号量只是使用数据来处理。

2.有名信号量的函数接口

1)创建并打开一个有名信号量  -> sem_open()

#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

2)有名信号量的P操作

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为止。

3)有名信号量的V操作

V操作: 资源数+1操作   -> sem_post()  -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
    sem:有名信号量的地址
返回值:
    成功:0    -> 资源数可以+1
    失败:-1


4)关闭有名信号量。   -> sem_close()  -> man 3 sem_close

#include <semaphore.h>
int sem_close(sem_t *sem);
参数:
    sem:有名信号量的地址
返回值:
    成功:0
    失败:-1

#include <semaphore.h>
int sem_unlink(const char *name);
参数:
    name: 有名信号量的名字
返回值:
    成功:0
    失败:-1

例子:使用有名信号量+共享内存实现 两个进程的通信。

说明:
    《编译有名信号量的时候需要加线程库来编译》

3.有名信号量与信号量的比较

不同点:
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;
}

七、同步互斥方式  --- 无名信号量

1.什么是无名信号量?

一般作用于线程之间的互斥,由于是无名信号量,所以说是没有名字的,不能使用sem_open打开。

2.无名信号量 函数接口

1)定义一个无名信号量  (数据类型: sem_t)

sem_t sem;   -> 无名信号量不是一个文件,而是一个变量。


2)初始化无名信号量。 -> sem_init()  -> man 3 sem_init

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
    sem: 无名信号量的变量的地址
    pshared:
        0  -> 作用于线程之间  -> 只考虑这种情况
        非0-> 作用于进程之间
    value: 无名信号量的起始值。
返回值:
    成功:0
    失败:-1


3)无名信号量的P操作

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为止。

4)无名信号量的V操作

V操作: 资源数+1操作   -> sem_post()  -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
    sem:无名信号量的地址
返回值:
    成功:0    -> 资源数可以+1
    失败:-1


5)销毁无名信号量。 -> sem_destroy()  -> man 3 sem_destroy

#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: 有一个进程,创建5个线程出来,每一个线程任务都是一样。
任务: 将"helloworld"字符串每隔1S打印一个字符。   -> 任务:10S
结果: hhhhheeeeellllllllllooooowwwwwooooorrrrrlllllddddd
    1S   1S   1S  1S    1S   1S  1S   1S    1S  1S

练习7: 有一个进程,创建5个线程出来,每一个线程任务都是一样。  (使用无名信号量去处理同步互斥)  -> 0是不能变成-1的
任务: 将"helloworld"字符串每隔1S打印一个字符。   -> 任务:10S
结果: helloworldhelloworldhelloworldhelloworldhelloworld
10S       10S      10S        10S       10S

答案:
练习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;
}


 类似资料: