【Linux】线程

“线程 对信号支持不好。”  ----黑马程序员
线程控制原语     进程控制原语
创建pthread_create()   fork();
得到idpthread_self()   getpid();
退出pthread_exit()   exit(); / return 
回收pthread_join()    wait()/waitpid()
杀死pthread_cancel()  kill()
线程分离pthread_detach()

创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。


线程之间共享和非共享资源

共享资源

  • 进程 ID 和父进程 ID

  • 进程组 ID 和会话 ID

  • 用户 ID 和 用户组 ID

  • 文件描述符表

  • 信号处置

  • 文件系统的相关信息:文件权限掩码(umask)、当前工作目录

  • 虚拟地址空间(除栈、.text)

非共享资源

  • 线程 ID

  • 信号掩码

  • 线程特有数据

  • error 变量

  • 实时调度策略和优先级

  • 栈,本地变量和函数的调用链接信息

.text

代码区中主要存放程序中的代码(二进制),属性是只读。

每个线程执行的代码可能不一样。每个线程的局部变量是不一样的;但,所有的线程都共享全局变量。

栈区(stack)

栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

vs运行效果如下


pthread_create 函数

一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。

程序中默认只有一个进程,fork()函数调用,2个进程。

程序中默认只有一个线程,pthread_create()函数调用,2个线程。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

pthread_t:typedef unsigned long int pthread_t;

void *(*start_routine) (void *) 的理解:参数是 void *,返回值也是 void * 的回调函数。

    - 功能:创建一个子线程

    - 参数:

        - thread:传出参数,线程创建成功后,子线程的线程ID 通过指针 被写到该变量中。

        - attr : 设置线程的属性,一般使用默认值,NULL

        - start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码。该函数运行结束,则线程结束。

        - arg : 给第三个参数使用,传参。传参是系统帮我们自动调用。

    - 返回值:

        成功:0

        失败:返回错误号。这个错误号和之前errno不太一样。Linux 环境下,所有线程特点,失败均直接返回错误号。

        获取错误号的信息:  char * strerror(int errnum);

代码:

void* tfn(void *arg){
	printf("pthread: pid = %d, tid = %lu \n", getpid(), pthread_self());
	return NULL;
}


// 主线程
int main(int argc, char* argv[])
{
	pthread_t tid;			// pthread_t --> unsigned long int


	printf("main: pid = %d, tid = %lu \n", getpid(), pthread_self());


    //创建一个子线程。tid 传出参数,传出的是子线程id。
	int ret = pthread_create(&tid, NULL, tfn, NULL);		

	printf("tid = %lu \n", tid);		// tid 是子线程id

	sleep(2);

	return 0;
}

这里main函数是主线程,tfn是子线程,pthread_create()是用来创建子线程。程序默认只有一个进程,进程pid=4495。主线程和子线程都是属于这个进程,但线程tid不同。


主线程与子线程之间的关系

主线程中开启了一个子线程,开启之后,主线程与子线程互不影响各自的生命周期,即主线程结束,子线程还可以继续执行;子线程结束,主线程也能继续执行。

对于程序来说,如果主进程在子进程还未结束时就已经退出,那么Linux内核会将子进程的父进程ID改为1(也就是init进程),当子进程结束后会由init进程来回收该子进程。

那如果是把进程换成线程的话,会怎么样呢?假设主线程在子线程结束前就已经退出,子线程会发生什么?

在一些论坛上看到许多人说子线程也会跟着退出,其实这是错误的,原因在于他们混淆了线程退出和进程退出概念。实际的答案是主线程退出后子线程的状态依赖于它所在的进程,如果进程没有退出的话子线程依然正常运转。如果进程退出了,那么它所有的线程都会退出,所以子线程也就退出了。

/*

    #include <pthread.h>
    void pthread_exit(void *retval);
        功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
        参数:
            retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。

    pthread_t pthread_self(void);
        功能:获取当前的线程的线程ID

    int pthread_equal(pthread_t t1, pthread_t t2);
        功能:比较两个线程ID是否相等
        不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的
        是使用结构体去实现的。
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);


    // 主线程
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    printf("main thread exit\n");

    // 退出进程,但由于主线程已经退出,这行代码不会执行。
    return 0;   // exit(0);
}

pthread_exit(NULL),是用来终止线程的,可以终止主线程和子线程。主线程结束是不影响子线程运行的。

exit(0),用来退出进程。如果进程退出了,那么它所有的线程都会退出,所以子线程也就退出了。

主函数中的 return 0;相当于exit(0); 

子函数中的 return NULL;相当于  pthread_exit(NULL);


pthread_join 函数


阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数。

int pthread_join(pthread_t thread, void **retval);

返回值:成功:0;失败:错误号

参数:thread:线程 ID (【注意】:不是指针);retval:存储线程结束状态。


对比记忆:
进程中:main 返回值、exit 参数-->int;等待子进程结束 wait 函数参数-->int *
线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **

struct pthread {
	int val;
	char str[256];
};


void *tfn(void *arg){
	struct pthread *t1;
	t1 = malloc(sizeof(struct pthread));	//线程退出了,线程函数内的栈(局部变量)会被回收,不用堆空间(malloc)会出错

	t1->val = 100;
	strcpy(t1->str, "hello pthread!");

	return (void*)t1;
}


int main(int argc, char* argv[])
{
	pthread_t tid;

	struct pthread *t;


	int ret = pthread_create(&tid, NULL, tfn, NULL);

	// int pthread_join(pthread_t thread, void **retval);
	pthread_join(tid, (void**)&t);		// waitpid,指定线程回收,并接受子线程返回状态。


	printf("child thread exit with var = %d, str = %s\n", t->val, t->str);

	// return 0;
	pthread_exit(NULL);		//pthread_exit() 函数只会终止当前线程,不会影响进程中其它线程的执行。
}

struct pthread {
	int val;
	char str[256];
};


// void的字面意思是“无类型”,void *则为“无类型指针”
void *tfn(void *arg){
	struct pthread *t1;
	t1 = (struct pthread *)arg;

	t1->val = 100;
	strcpy(t1->str, "hello pthread!");

	return (void*)t1;
}


int main(int argc, char* argv[])
{
	pthread_t tid;

	struct pthread arg;
	struct pthread *t;


	// int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
	int ret = pthread_create(&tid, NULL, tfn, (void *) &arg);


	// int pthread_join(pthread_t thread, void **retval);
	pthread_join(tid, (void**)&t);		// waitpid,指定线程回收,并接受子线程返回状态。

	printf("child thread exit with var = %d, str = %s\n", arg.val, arg.str);

	printf("child thread exit with var = %d, str = %s\n", t->val, t->str);

	// return 0;
	pthread_exit(NULL);		//pthread_exit() 函数只会终止当前线程,不会影响进程中其它线程的执行。
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值