进程:独立地址空间,拥有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;