Linux-线程终止-线程等待-线程分离-线程安全
Posted 天津 唐秙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-线程终止-线程等待-线程分离-线程安全相关的知识,希望对你有一定的参考价值。
文章目录
1. 线程终止
1.1 pthread_exit函数
void pthread_exit(void *retval);
作用: 谁调用谁退出
参数:
retval:线程A结束的时候传递给等待线程B的参数
验证:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define a 4
struct ThreadId
{
int thread_id_;
};
void* MyThreadStart(void* arg)
{
struct ThreadId* ti = (struct ThreadId*)arg;
printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
sleep(1);
pthread_exit(NULL);
printf("pthread_exit, i = %d\\n", ti->thread_id_);
delete ti;
}
int main()
{
pthread_t tid;
for(int i = 0; i < a; i++)
{
struct ThreadId* ti = new ThreadId();
ti->thread_id_ = i;
int ret = pthread_create(&tid,NULL,MyThreadStart,(void*)ti);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
//创建成功
while(1)
{
printf("i am main thread\\n");
sleep(1);
}
return 0;
}
如果线程退出,打印如图所示,如果线程未退出,每一个线程会打印两句话。
1.2 线程的入口函数代码执行完毕,线程退出
1.3 pthread_cancel函数
int pthread_cancel(pthread_t thread);
功能: 根据线程的标识符终止对应线程
参数:
thread:被终止的线程的标识符
获取自己的线程标识符: pthread_t pthread_self(void);
验证:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 4
struct ThreadId
{
int thread_id_;
};
void* MyThreadStart(void* arg)
{
struct ThreadId* ti = (struct ThreadId*)arg;
//while(1)
{
printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
sleep(1);
}
pthread_cancel(pthread_self());
delete ti;
}
int main()
{
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
struct ThreadId* ti = new ThreadId();
ti->thread_id_ = i;
int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*) ti);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
//pthread_cancel(pthread_self());
while(1)
{
printf("i am main thread\\n");
sleep(1);
}
return 0;
}
如果主线程自己取消自己,调用pthread_cancel(pthread_self),那么主线程的状态变为僵尸状态(Z),工作线程正常,整个进程没有退出。
验证:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 4
struct ThreadId
{
int thread_id_;
};
void* MyThreadStart(void* arg)
{
struct ThreadId* ti = (struct ThreadId*)arg;
while(1)
{
printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
sleep(1);
}
delete ti;
}
int main()
{
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
struct ThreadId* ti = new ThreadId();
ti->thread_id_ = i;
int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
pthread_cancel(pthread_self());
while(1)
{
printf("i am main thread\\n");
sleep(1);
}
return 0;
}
2. 线程等待
2.1 原因
由于线程的默认属性为joinable属性,当线程退出的时候其资源不会被操作系统回收,需要其他线程来进行进程等待,继续回收,否则就会造成内存泄露。
2.2 接口
int pthread_join(pthread_t thread, void **retval);
thread:需要等待的线程的标识符
retval:线程退出时的返回值
1.线程入口函数退出的时候,retval就是线程入口函数的返回值
2.pthread_exit(void* retval),retval就是pthread_exit函数的参数值
3.pthread_cancel:retval的值是一个常数PTHREAD_CANCELED,调用pthread_join函数进行等待的执行流如果还没有等待到退出的线程,则当前调用pthread_join函数的执行流就会阻塞。
2.3 验证
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 4
struct ThreadId
{
int thread_id_;
};
void* MyThreadStart(void* arg)
{
struct ThreadId* ti = (struct ThreadId*)arg;
while(1)
{
printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
sleep(1);
}
delete ti;
return NULL;
}
int main()
{
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
struct ThreadId* ti = new ThreadId();
ti->thread_id_ = i;
int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
for(int i = 0; i < THREAD_NUM; i++)
{
pthread_join(tid[i], NULL);
}
while(1)
{
printf("i am main thread\\n");
sleep(1);
}
return 0;
}
3. 线程分离
3.1 概念
一个进程的属性如果从joinable属性变成detach属性,则当前这个线程在退出的时候,不需要其他线程回收资源,操作系统会自己回收资源。
3.2 接口
int pthread_detach(pthread_t thread);
参数: pthread_t thread
如果工作线程自己分离自己,要写在工作线程代码的最前面。
3.3 验证
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 4
struct ThreadId
{
int thread_id_;
};
void* MyThreadStart(void* arg)
{
pthread_detach(pthread_self());
struct ThreadId* ti = (struct ThreadId*)arg;
//while(1)
{
printf("i am MyThreadStart, i = %d\\n", ti->thread_id_);
sleep(1);
}
delete ti;
return NULL;
}
int main()
{
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
struct ThreadId* ti = new ThreadId();
ti->thread_id_ = i;
int ret = pthread_create(&tid[i], NULL, MyThreadStart, (void*)ti);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
while(1)
{
printf("i am main thread\\n");
sleep(1);
}
return 0;
}
4. 线程安全
4.1 概念
多个执行流,访问临界资源,不会导致程序产生二义性。
执行流: 理解为线程
访问: 指的是对临界资源进行操作
临界资源: 指的是多个线程都可以访问到的资源
例如:全局变量,某个结构体变量,某个类的实例化指针
临界区: 代码操作临界资源的代码区域称之为临界区
二义性: 结果会有多个
4.2 原理
正常来说,第一步,内存当中有一个i = 10,寄存器从内存中读到i = 10,第二步,CPU从寄存器中拿到i的值进行计算,第三步,CPU将计算的结果写回到寄存器,第四步,寄存器将写回的值在内存中进行修改。
4.3 线程不安全的现象
内存中我们有一个i = 10,我们假设有两个线程,两个线程都是对于i进行加1操作。线程1先开始执行,当线程1把i读到寄存器后,由于计算机是分时轮转,这时候时间片用完了,线程1被切换出去了,此时线程1在寄存其中i的值是10,之后线程2被切换进来了,线程2从内存当中读到i的值为10,经过CPU进行运算,写回到寄存器,寄存器再存到内存当中,这时候i在内存当中的值被修改为了11,线程2结束后,线程1又被切换回来了,线程1通过保存的上下文信息知道i此时的值是10,通过程序计数器知道要在CPU当中进行加1操作,经过CPU进行运算,写回到寄存器,寄存器再存到内存当中,这时候i在内存当中的值仍然是11,但是实际上我们对i执行了两次加1操作,正确的值是12,这时候代码的结果就存在了二义性。
4.4 验证
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 2
int g_tickets = 10;
void* MyThreadStart(void* arg)
{
while(1)
{
if(g_tickets > 0)
{
printf("i have %d, i am %p\\n", g_tickets, pthread_self());
g_tickets--;
}
else
{
pthread_exit(NULL);
}
}
return NULL;
}
int main()
{
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
int ret = pthread_create(&tid[i], NULL, MyThreadStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
for(int i = 0; i < THREAD_NUM; i++)
{
pthread_join(tid[i], NULL);
}
printf("pthread_join end...\\n");
return 0;
}
以上是关于Linux-线程终止-线程等待-线程分离-线程安全的主要内容,如果未能解决你的问题,请参考以下文章