Linux线程基础知识

进程:独立地址空间,拥有PCB。进程可以蜕变成线程,进程创建线程后就蜕变成了线程。

线程:轻量级进程(light-weight process,LWP)有独立的PCB,但没有独立的地址空间(共享)

区别:是否共享地址空间

Linux下:线程:最小的执行单位。A进程创建了3个线程,这3个线程和其他B、C进程共同竞争CPU。

                进程:最小资源分配单位,可看成是只有一个线程的进程

ps -Lf 进程id --->线程号。  LWP -->cpu执行的最小单位

查看firefox进程31515有多少个线程:

 在CPU眼里,线程和进程同等的,竞争CPU。

独享栈空间(内核栈,用户栈),共享全局变量。

线程操作函数是库函数,不是系统调用。

优先用线程,简单。

线程控制原语

编译时增加-pthread选项;线程函数返回值一般情况:成功:0,失败:返回错误码,不使用errno(Linux下,所有线程特点,失败均直接返回错误号)

1. 获取线程id

pthread_t pthread_self(void);

获取线程id,对应进程中getpid()函数。线程id是在进程地址空间内部,用来标识线程身份的id号

返回值:本线程id

线程id:pthread_t类型,本质上,在Linux下为无符号整数(%lu),其他系统中可能是结构体实现

线程id是进程内部识别标志(两个进程内,线程id运行相同)

2.创建线程:

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

创建一个新线程,对应进程fork()函数。pthread_t:在当前Linux中,可以理解为,typedef unsigned long int pthread_t;

参数一:传出参数,保存创建的子线程的id

参数二:设置创建的线程属性,比如栈大小,调度参数,初始分离状态。一般保持默认,设置为NULL

参数三:回调函数,创建成功,pthread_create函数返回时,该函数会自动调用

参数四:传递给回调函数的参数,没有的话设置为NULL

返回值:成功:0,失败:errno,Linux下,所有线程特点,失败均直接返回错误号

 例子:循环创建线程,输出是创建的第几个线程:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>


void * start_thread(void *arg)
{
	int i = (int)arg;
	printf("the %dth thread created successfully,tid=%lu,pid=%d\n",i,pthread_self(),getpid());
	return NULL;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	int ret;
	for(int i=1;i<6;i++)
	{
		ret = pthread_create(&tid,NULL,start_thread,(void *)i);
		if(ret!=0)
		{
			perror("pthread_create error");
		}
	}
	printf("主线程tid=%lu,pid=%d\n",pthread_self(),getpid());
	sleep(5);
	return 0;
}

这里将整数强转为void*类型。

下面这种方法不经意间容易出错:把i的地址传递给子进程,但是在主进程当中i的值也在变,所以会出错:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>


void * start_thread(void *arg)
{
	int i = *(int *)arg;
	printf("the %dth thread created successfully,tid=%lu,pid=%d\n",i,pthread_self(),getpid());
	return NULL;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	int ret;
	for(int i=1;i<6;i++)
	{
		ret = pthread_create(&tid,NULL,start_thread,(void *)&i);
		if(ret!=0)
		{
			perror("pthread_create error");
		}
	}
	printf("主线程tid=%lu,pid=%d\n",pthread_self(),getpid());
	sleep(5);
	return 0;
}

3.将单个线程退出

void pthread_exit(void *retval);

retval:退出值。不需要退出值时,设置为NULL

return:返回到调用者那里去

pthread_exit():将调用该函数的线程退出

exit:将进程退出

          exit状态值(这个我老是记不住)

#define	EXIT_FAILURE	1	/* Failing exit status.  */
#define	EXIT_SUCCESS	0	/* Successful exit status.  */

多线程环境中,尽量不要用exit函数,主控线程退出时也不能return,exit。

在主线程中使用pthread_exit将自己退出后,子线程不受影响,继续执行。

//创建的第三个线程直接退出
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>


void * start_thread(void *arg)
{
	int i = (int)arg;
	if(i==3)
	{
		pthread_exit(NULL);//这里不能使用exit(0);
	}
	printf("the %dth thread created successfully,tid=%lu,pid=%d\n",i,pthread_self(),getpid());
	return NULL;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	int ret;
	for(int i=1;i<6;i++)
	{
		ret = pthread_create(&tid,NULL,start_thread,(void *)i);
		if(ret!=0)
		{
			perror("pthread_create error");
		}
	}
	printf("主线程tid=%lu,pid=%d\n",pthread_self(),getpid());
	sleep(5);
	return 0;
}

注意:线程中不能使用exit函数,exit会退出整个进程

将pthread_exit(NULL)改为exit(0);后,程序就直接退出了。

子线程可以在调用的函数中使用 pthread_exit(NULL)基本自己,例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>


void myfun(void)
{
	pthread_exit(NULL);
}

void * start_thread(void *arg)
{
	int i = (int)arg;
	if(i==3)
	{
		//pthread_exit(NULL);
		//exit(0);
		myfun();
	}
	printf("the %dth thread created successfully,tid=%lu,pid=%d\n",i,pthread_self(),getpid());
	return NULL;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	int ret;
	for(int i=1;i<6;i++)
	{
		ret = pthread_create(&tid,NULL,start_thread,(void *)i);
		if(ret!=0)
		{
			perror("pthread_create error");
		}
	}
	printf("主线程tid=%lu,pid=%d\n",pthread_self(),getpid());
	sleep(5);
	return 0;
}

4.阻塞等待线程退出,获取线程退出状态

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

1.如果线程通过return返回,retval存储的是thread线程函数的返回值

2.如果线程被别的线程调用pthread_cancel异常终止掉,retval存储的是常数PTHREAD_CANCELED

3.如果线程是自己调用pthread_eixt终止的,retval存储的是传递给pthread_exit的参数

4.不需要参数的话,直接传NULL给retval参数。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

struct student
{
	int age;
	char name[20];
};

void * start_thread(void *arg)
{
	struct student *stu = (struct student*)malloc(sizeof(struct student));
	stu->age = 25;
	strcpy(stu->name,"huangtieniu");
	return (void *)stu;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	struct student *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}
	ret = pthread_join(tid,(void*)&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("child thread eixt with age=%d,name=%s\n",retval->age,retval->name);
	return 0;
}

程序比较简单,下面一种写法当然是错误的,不能返回局部变量地址

void * start_thread(void *arg)
{
	struct student stu;
	stu.age = 25;
	strcpy(stu.name,"huangtieniu");
	return (void *)&stu;
}

当然,下面这种写法是对的:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

struct student
{
	int age;
	char name[20];
};

void * start_thread(void *arg)
{
	struct student *stu = (struct student *)arg;
	stu->age = 25;
	strcpy(stu->name,"huangtieniu");
	return (void *)stu;
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	struct student *arg;
	struct student *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,(void *)arg);
	if(ret!=0)
	{
		perror("pthread_create error");
	}
	ret = pthread_join(tid,(void*)&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("child thread eixt with age=%d,name=%s\n",retval->age,retval->name);
	return 0;
}

pthread_exit()传递参数给pthread_join,程序输出 hello world。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void * start_thread(void *arg)
{
	
	pthread_exit("hello world");
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	
	char *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}
	ret = pthread_join(tid,(void*)&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("%s\n",retval);
	
	return 0;
}

5.杀死(取消)线程

int pthread_cancel(pthread_t thread);

对应进程中kill()函数。

pthread_cancel杀死线程,并不是实时的,需要等待线程到达取消点(保存点),如果子线程没有到达取消点,pthread_cancel函数无效,可以自己在程序中,手动添加一个取消点。使用pthread_testcancel()函数。

被pthread_cancel()杀死的线程,返回(void *)-1,使用pthread_join回收。

#define PTHREAD_CANCELED ((void *) -1)

取消点:通常是一些系统调用,

(1).普通情况,使用pthread_cancel杀死线程

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void * start_thread(void *arg)
{
	
	while(1)
	{
		printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		sleep(1);
	}
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	void *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}

	sleep(3);
	ret = pthread_cancel(tid);
	if(ret!=0)
	{
		perror("pthread_cancel error");
	}

	ret = pthread_join(tid,&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("%d\n",(int)retval);
	
	//pthread_exit(NULL);
	return 0;
}

(2).没有取消点,使用pthread_cancel不能杀死线程

如果把start_thread函数中下面两行代码注释掉,线程就不能到达取消点,所以pthread_cancel就不能杀死线程。

void * start_thread(void *arg)
{
	
	while(1)
	{
		//printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		//sleep(1);
	}
}

 

(3).自己人为添加取消点,在上面函数while(1)中添加函数pthread_testcancel();使线程能到达取消点。

void * start_thread(void *arg)
{
	
	while(1)
	{
		//printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		//sleep(1);
		pthread_testcancel();
	}
}

(4).pthread_cancel不是非阻塞的,没有取消点就没有,程序接着执行

把pthread_join后面代码注释,程序会终止。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void * start_thread(void *arg)
{
	
	while(1)
	{
		//printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		//sleep(1);
		//pthread_testcancel();
	}
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	void *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}

	sleep(3);
	ret = pthread_cancel(tid);
	if(ret!=0)
	{
		perror("pthread_cancel error");
	}

	/*
	ret = pthread_join(tid,&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("%d\n",(int)retval);
	*/
	
	//pthread_exit(NULL);
	return 0;
}

(5)线程自己调用pthread_cancel杀死自己。不过一般不这样干吧,用pthread_exit就好。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void * start_thread(void *arg)
{
	
	while(1)
	{
		printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		sleep(1);
		//pthread_testcancel();
		int ret = pthread_cancel(pthread_self());
		if(ret!=0)
		{
			perror("pthread_cancel error");
		}
	}
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	void *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}	

	ret = pthread_join(tid,&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("%d\n",(int)retval);
	
	//pthread_exit(NULL);
	return 0;
}

打印输出了两次,如果把下面两行代码换个位置,只会输出一次

void * start_thread(void *arg)
{
	
	while(1)
	{
                sleep(1);
		printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		//pthread_testcancel();
		int ret = pthread_cancel(pthread_self());
		if(ret!=0)
		{
			perror("pthread_cancel error");
		}
	}
}

可以看出pthread_cancel在到达取消点的时候就杀死线程。

6.pthread_cancel补充

线程是否可以被取消以及如何取消取决于取消状态和取消类型。

取消状态:有两种,允许和不允许,PTHREAD_CANCAL_ENABLE表示允许,PTHREAD_CANCEL_DISABLE表示不允许。

通过函数pthread_setcancelstate设置取消状态,线程默认取消状态是运行。

int pthread_setcancelstate(int state, int *oldstate);

 新状态设置成state,老状态保存到oldstate中。

取消类型:两种,异步或者延迟,默认是延迟。异步表示,发出取消请求后,线程可能会在任何点背杀死;延迟表示,线程只会在特定的取消点被杀死。异步只在某些特定场景下使用,因为它使得进程处于未知状态。

PTHREAD_CANCEL_ASYNCHRONOUS表示异步,PTHREAD_CANCEL_DEFERRED表示延迟,使用函数pthread_setcanceltype设置取消类型。

int pthread_setcanceltype(int type, int *oldtype);
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void * start_thread(void *arg)
{
	int unused;
	int ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&unused);//默认
	if(ret)
	{
		perror("pthread_setcancelstate error");
	}

	ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&unused);//默认
	if(ret)
	{
		perror("pthread_setcanceltype error");
	}	

	while(1)
	{
		printf("thread:pid=%d,tid=%lu\n",getpid(),pthread_self());
		sleep(1);
		//pthread_testcancel();
	    ret = pthread_cancel(pthread_self());
		if(ret!=0)
		{
			perror("pthread_cancel error");
		}
	}
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	void *retval;//用于接收子线程的返回值

	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret!=0)
	{
		perror("pthread_create error");
	}	

	ret = pthread_join(tid,&retval);
	if(ret!=0)
	{
		perror("pthread_join error");
	}
	printf("%d\n",(int)retval);
	
	pthread_exit(NULL);
	//return 0;
}

 

7.设置线程分离

int pthread_detach(pthread_t thread);

设置线程分离后,线程结束后,其退出状态不由其他线程获取,而直接自己自动释放,网络,多线程服务器常用。

设置线程分离后,就不能再调用pthread_join接收该线程了,不然会报参数错误。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

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

int main(int argc, char const *argv[])
{
	
	pthread_t tid;
	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret)
	{
		fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
	}	

	ret = pthread_detach(tid);
	if(ret)
	{
		fprintf(stderr, "pthread_detach error:%s\n", strerror(ret));
	}	
	sleep(2);
	ret = pthread_join(tid,NULL);
	//printf("%lu\n",tid);
	if(ret)
	{
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
	}	
	
	//pthread_exit(NULL);
	return 0;
}

主线程执行到pthread_join的时候,就会报错,而不是等子线程结束后,再执行pthread_join发现不能接收报错。

在线程中,不能使用errno,因为线程函数出错的时候,没有设置errno。

把上面中错误改成使用errno形式,报错:

if(ret)
{
      perror("pthread_join error");
}	

如果 一定要用errno,那就笨一点:使用前给errno赋值

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

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

int main(int argc, char const *argv[])
{
	
	pthread_t tid;
	int ret = pthread_create(&tid,NULL,start_thread,NULL);
	if(ret)
	{
		errno = ret;
		perror("pthread_create error");
	}	

	ret = pthread_detach(tid);
	if(ret)
	{
		errno = ret;
		perror("pthread_detach error");
	}	
	sleep(2);
	ret = pthread_join(tid,NULL);
	//printf("%lu\n",tid);
	if(ret)
	{
		errno = ret;
		perror("pthread_join error");
	}	
	
	//pthread_exit(NULL);
	return 0;
}

8.设置线程属性

其他的属性设置用的比较少,设置线程分离属性用的较多。

属性值不能直接设置,需要使用相关函数进行操作。

线程属性初始化:

int pthread_attr_init(pthread_attr_t *attr);

销毁线程属性结构

int pthread_attr_destroy(pthread_attr_t *attr);

设置线程分离属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

获取线程分离属性

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

detachstate有两种:PTHREAD_CREATE_DETACHED(分离),PTHREAD_CREATE_JOINABLE(不分离)

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

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

int main(int argc, char const *argv[])
{
	
	pthread_t tid;
	pthread_attr_t attr;
	int ret;
	ret = pthread_attr_init(&attr);
	if(ret)
	{
		errno = ret;
		perror("pthread_attr_init error");
	}	
	ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	if(ret)
	{
		errno = ret;
		perror("pthread_attr_setdetachstate error");
	}	
	ret = pthread_create(&tid,&attr,start_thread,NULL);
	if(ret)
	{
		errno = ret;
		perror("pthread_create error");
	}	
	ret = pthread_attr_destroy(&attr);
	if(ret)
	{
		errno = ret;
		perror("pthread_attr_destroy error");
	}	
	//sleep(2);
	ret = pthread_join(tid,NULL);
	//printf("%lu\n",tid);
	if(ret)
	{
		errno = ret;
		perror("pthread_join error");
	}	
	
	pthread_exit(NULL);
	//return 0;
}

使用线程注意事项:

 pthread_mutex_t:是一个联合体,定义在/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h

typedef union
{
  struct __pthread_mutex_s __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值