Linux之线程

一 . 线程的概念

线程概念:
进程:有独立的 进程地址空间。有独立的pcb。 分配资源的最小单位。
线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。
在linux下:
线程就是进程,轻量级的进程。
对于内核来说,线程就是进程。
在这里插入图片描述

主线程和子线程共享和不共享的区域
共享
	.text 
	.bss
	.data
	堆
	动态库加载区
	环境变量
	命令行参数
	通信:全局变量,堆
不共享:
	如果有五个线程
	栈区被平分为5份
	 所以线程之间通信,不可以用局部变量(因为局部变量在栈里)

二. 线程相关操作与函数

1. 创建函数

pthread_create线程创建函数

int pthread_create(
	pthread_t *tid, 
	const pthread_attr_t *attr,
 	void *(*start_rountn)(void *), 
 	void *arg); 
参数:
	pthread_t *tid:传出参数,表新创建的子线程 id
	const pthread_attr_t *attr:线程属性。传NULL表使用默认属性。
	void *(*start_rountn)(void *):子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
		void *arg:参3的参数。没有的话,传NULL
返回值:
	成功:0
	失败:errno

pthread_self函数获取线程id

pthread_t pthread_self(void);	
获取线程id。 线程id是在进程地址空间内部,用来标识线程身份的id号。
		返回值:本线程id

示例1:创建线程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h> //这里是线程对应的头文件
 
void* myfunc(void* arg)
{
    printf("child pthread id: %lu\n", pthread_self());
    return NULL;
}
 
int main(int argc, const char* argv[])
{
    // 创建子线程
    pthread_t thid;
    // 返回错误号
    int ret = pthread_create(&thid, NULL, myfunc, NULL);
    if(ret != 0)
    {
        printf("error number: %d\n", ret);
        // 根据错误号打印错误信息
        printf("error information: %s\n", strerror(ret));
    }
    printf("parent pthread id: %lu\n", pthread_self());
	//这里sleep是因为,父线程和子线程也会竞争cpu。
	//如果父线程先结束,那么则整个程序释放,子线程来不及执行,就结束了
    sleep(1);
    return 0;
}

运行结果:
在这里插入图片描述
示例2:循环创建多个子线程

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
void sys_err(const char *str){  
    perror(str);  
    exit(1);  
}  
  
void *tfn(void *arg){  
    int i = (int)arg;  
    sleep(i);  
    printf("--I'm %dth thread: pid = %d, tid = %lu\n",i+1, getpid(), pthread_self());
    return NULL;  
}  
  
int main(int argc, char *argv[]){  
    int i;  
    int ret;  
    pthread_t tid;        
    for(i=0;i<5;i++){  
        ret = pthread_create(&tid, NULL, tfn, (void *)i);  
        if (ret != 0) {  
            sys_err("pthread_create error");  
        }  
    }  
    sleep(i);  
    printf("I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());    
    return 0;  
}  

运行结果:
在这里插入图片描述
如果将i取地址后再传入线程创建函数里,就是说

当前传的是:(void *)i
改成:	  (void *)&i

相应的,修改回调函数:int i = *((int *)arg)
运行代码,会出现如下结果:
在这里插入图片描述
如果多次运行都只有主线程的输出,将主线程等待时长从i改为大于6的数即可。因为子线程等待时间i是不定的,但都小于等于6秒,由于抢cpu时没抢过主线程,导致没有子线程的输出。错误原因在于,子线程如果用引用传递i,会去读取主线程里的i值,而主线程里的i是动态变化的,不固定。所以,应该采用值传递,不用引用传递。

2. 线程全局变量共享

子线程里更改全局变量后,主线程里也跟着发生变化。
在这里插入图片描述
运行结果:
在这里插入图片描述

3. 退出函数

pthread_exit退出线程

void pthread_exit(void *retval);  退出当前线程。
			参数:retval:退出值。 无退出值时,NULL
比较:
exit();	退出当前进程。
return: 返回到调用者那里去。
pthread_exit(): 退出当前线程。

示例:pthresd_exit.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h> //这里是线程对应的头文件
 
void* myfunc(void* arg)
{
	//打印子进程的id
    printf("child pthread id: %lu\n", pthread_self());
    for(int i=0;i<5;++i)
	{
		printf("child i = %d\n",i);
	}
	return NULL;
}
 
int main(int argc, const char* argv[])
{
    // 创建子线程
    //线程ID变量
	int ret = pthread_create(thid, NULL, myfunc, NULL); 
	if(ret != 0)
	{
		printf("error number: %d\n",ret);
		//打印错误信息
		printf("%s\n",strerror(ret));
	}
    printf("parent pthread id: %lu\n", pthread_self());
	
	//退出主线程
	pthread_exit(NULL);
	
    for(int i=0;i<5;++i)
	{
		printf("parent child i = %d\n",i);
	}
    return 0;
}

运行结果,由于pthread_exit()让主线程退出,所以主程序最后的循环部分,并不会执行
在这里插入图片描述

4. 回收线程

pthread_join函数:阻塞回收线程

int pthread_join(pthread_t thread, void **retval);
参数:
	thread: 待回收的线程id
	retval:传出参数。 回收的那个线程的退出值。
			线程异常借助,值为 -1。
返回值:
	成功:0
	失败:errno

示例:pthread_join.c

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
struct thrd {  
    int var;  
    char str[256];  
};  
  
void sys_err(const char *str)  
{  
    perror(str);  
    exit(1);  
}  
  
void *tfn(void *arg)  
{  
    struct thrd *tval;    
    tval = malloc(sizeof(tval));  
    tval->var = 100;  
    strcpy(tval->str, "hello thread");  
    return (void *)tval;  
}  
  
int main(int argc, char *argv[])  
{  
    pthread_t tid;  
    struct thrd *retval;  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  
    if (ret != 0)  
        sys_err("pthread_create error");  
    //int pthread_join(pthread_t thread, void **retval);  
    ret = pthread_join(tid, (void **)&retval);  
    if (ret != 0)  
        sys_err("pthread_join error");  
    printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);  
     
    pthread_exit(NULL);  
  
}  

编译运行,结果如下:最后返回了线程的返回值。
在这里插入图片描述

5. 杀死线程

pthread_cancel函数:杀死一个线程。
注意事项:在要杀死的子线程对应的处理函数内部
必须做一次系统调用。

int pthread_cancel(pthread_t thread);		
参数:
	thread: 待杀死的线程id
返回值:
	成功:0
	失败:errno
如果,子线程没有到达取消点, 那么 pthread_cancel 无效。
我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();
成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。

示例:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
void *tfn(void *arg){  
    while (1) {  
        printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
        sleep(1);  
    }   
    return NULL;  
}  
  
int main(int argc, char *argv[]){  
    pthread_t tid;  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  
    if (ret != 0) {  
        fprintf(stderr, "pthread_create error:%s\n", strerror(ret));  
        exit(1);  
    }  
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
    sleep(5);  
    
    ret = pthread_cancel(tid);          // 终止线程  
    if (ret != 0) {  
        fprintf(stderr, "pthread_cancel error:%s\n", strerror(ret));  
        exit(1);  
    }  
    while (1);  
    pthread_exit((void *)0);  
}  

运行结果:
在这里插入图片描述
可以看到,主线程确实kill了子线程。这里要注意一点,pthread_cancel工作的必要条件是进入内核,如果tfn没有进入内核,则pthread_cancel不能杀死线程,此时需要手动设置取消点,就是pthread_testcancel()

6. 线程分离

pthread_detach函数设置线程分离,调用该函数之后,不需要在利用Pthread函数进行线程回收。子线程会自动回收自己的pcb。

int pthread_detach(pthread_t thread);
参数:
	thread: 待分离的线程id
返回值:
	成功:0
	失败:errno
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
  
void *tfn(void *arg)  
{  
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
    return NULL;  
}  
  
int main(int argc, char *argv[])  
{  
    pthread_t tid;  
  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  
    if (ret != 0) {  
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));  
        exit(1);  
    }  
    ret = pthread_detach(tid);              // 设置线程分离` 线程终止,会自动清理pcb,无需回收  
    if (ret != 0) {  
        fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));  
        exit(1);  
    }  
  
    sleep(1);  
    ret = pthread_join(tid, NULL);  
    printf("join ret = %d\n", ret);  
    if (ret != 0) {  
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));  
        exit(1);  
    }  
  
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
    pthread_exit((void *)0);  
}  

编译运行,结果如下:
在这里插入图片描述
线程分离后,系统会自动回收资源,用pthread_join去回收已经被系统回收的线程,那个线程号就是无效参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值