开辟进程会分配新的地址空间,系统开销高。
每个进程可以有很多线程,同个进程的线程共享地址空间,共享全局变量和对象,系统开销较低。
头文件
#include <pthread.h>
pid类型
pid类型
pthread_t
,实质是unsigned long int
,一串长长的无符号整数链接
要指定pthread共享库
g++ -o demo demo.cpp -lpthread
查看线程
ps -xH|grep demo
(1)线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
thread: 指向 pthread_t 类型的指针,用于存储新创建的线程的标识符。
attr: 指向 pthread_attr_t 结构的指针,用于指定线程的属性。如果不需要特定属性,可以传递 NULL。(可设置detach)
start_routine: 线程将要执行的主函数。void* func(void*)
arg: 传递给 start_routine 函数的参数。
返回值:
成功时,返回 0。
失败时,返回错误代码。
//开发用的少,一般用于测试,因为主线程有其他事要做不可能等待子线程
int pthread_join(pthread_t thread, void **retval);
thread: 要等待的线程的标识符。
retval(returnvalue): 一个指向 void* 类型的指针的指针。如果线程函数返回了一个值,并且你希望获取这个返回值,可以传递一个非 NULL 的指针给 retval。如果不需要获取返回值,可以传递 NULL。
返回值:
成功时,返回 0。
失败时,返回错误代码。
(2)线程终止
因为在进程的任一线程中exit()
,整个进程会结束,所以线程的start_routine
函数中不能调用exit()
-
线程的start_routine函数执行完,自然终止
-
start_routine函数中调用
pthread_exit
void pthread_exit(void *retval); //retval通常是一个整数
-
被主进程或其他线程终止
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* mainfunc1(void* arg);
void* mainfunc2(void* arg);
int val=0;
int main()
{
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,mainfunc1,NULL)!=0)
printf("create thread failed..");
if(pthread_create(&pthid2,NULL,mainfunc2,NULL)!=0)
printf("create thread failed..");
printf("pthid1=%lu,pthid2=%lu\n",pthid1,pthid2);
printf("等待子线程退出\n");
pthread_join(pthid1,NULL);
printf("子线程1退出\n");
pthread_join(pthid2,NULL);
printf("子线程2退出\n");
return 0;
}
void* mainfunc1(void* arg)
{
for(int i=0;i<5;i++)
{
val++;
sleep(1);
printf("子线程1(%d)\n",val);
}
pthread_exit(0);
}
void* mainfunc2(void* arg)
{
for(int i=0;i<30;i++)
{
val++;
sleep(1);
printf("子线程2(%d)\n",val);
}
}
(3)参数传递
不传参数而直接用全局变量时,全局变量被主线程改变,线程也不是一创建就立即运行,运行时可能用到的变量已经被其他线程或主线程改了。
pthread_create
的第4个参数。
-
强转
指针8字节,int4字节,long8字节。
只要字节长度保持一致,就可以转。
//指针可以存数值 int i=10; void* ptr=(void*)(long)i; //int是4字节,先转long是8字节,再转指针8字节 int j=(int)(long)ptr; long i=10; void* ptr=(void*)i; int j=(long)ptr; long i=-10; //可以存负值 void* ptr=(void*)i; int j=(long)ptr;
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
void* mainfunc1(void* arg)
{
printf("线程%d\n",(int)(long)arg);
for(int i=0;i<5;i++)
{
sleep(1);
}
pthread_exit(0);
}
void* mainfunc2(void* arg)
{
printf("线程%d\n",*((int*)arg));
for(int i=0;i<5;i++)
{
sleep(1);
}
pthread_exit(0);
}
void* mainfunc3(void* arg)
{
printf("线程%d\n",(int)(long)arg);
for(int i=0;i<5;i++)
{
sleep(1);
}
pthread_exit(0);
}
void* mainfunc4(void* arg)
{
printf("线程%d\n",(int)(long)arg);
for(int i=0;i<5;i++)
{
sleep(1);
}
pthread_exit(0);
}
void* mainfunc5(void* arg)
{
printf("线程%d\n",(int)(long)arg);
for(int i=0;i<5;i++)
{
sleep(1);
}
pthread_exit(0);
}
int val=1;
int main()
{
pthread_t pthid1,pthid2,pthid3,pthid4,pthid5;
if(pthread_create(&pthid1,NULL,mainfunc1,(void*)(long)val)!=0)
printf("thread create failed..");
val++;
if(pthread_create(&pthid2,NULL,mainfunc2,&val)!=0)
printf("thread create failed..");
val++;
if(pthread_create(&pthid3,NULL,mainfunc3,(void*)(long)val)!=0)
printf("thread create failed..");
val++;
if(pthread_create(&pthid4,NULL,mainfunc4,(void*)(long)val)!=0)
printf("thread create failed..");
val++;
if(pthread_create(&pthid5,NULL,mainfunc5,(void*)(long)val)!=0)
printf("thread create failed..");
pthread_join(pthid1,NULL);
printf("子线程1退出\n");
pthread_join(pthid2,NULL);
printf("子线程2退出\n");
pthread_join(pthid3,NULL);
printf("子线程3退出\n");
pthread_join(pthid4,NULL);
printf("子线程4退出\n");
pthread_join(pthid5,NULL);
printf("子线程5退出\n");
return 0;
}
(4)线程资源回收
线程有两种状态:joinable(非分离)、unjoinble(分离)。
创建线程默认是joinable。
joinable状态的线程在主函数执行完后(自己退出或调用pthread_exit()退出)不会自己释放内存资源,这种线程称为“僵尸线程”。
回收方法:
-
主进程pthread_join。(不常用)
实际中一般不会这么用,因为主进程要干别的,不能阻塞在pthread_join这
-
创建线程前,把将要创建的线程设为分离
pthread_create
的第2个参数。线程设置分离后就join不到了,返回22,不返回0。
pthread_attr_t attr; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_create(&pthid, &attr, pth_main, (void*)((long)TcpServer.m_clientfd));
-
创建线程后在主线程中调用
pthread_detach(pthid)
设置分离 -
在子线程中调用
pthread_detach(pthread_self())
设置分离
(5)线程返回状态
如果主线程需要使用子线程的返回值,需要等待子线程执行完毕。
子线程的主函数结束时,通过pthread_join
第二个参数获取它的返回状态。
虽然第二个参数类型是void**
,但还是指针,也就是8字节,存储据强转就行:
return 0
因为在C中NULL就是0,所以不用强转。而return 10
需要强转为return (void*)10
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* mainfunc1(void* arg);
int main()
{
pthread_t pthid;
int ival;
pthread_create(&pthid,NULL,mainfunc1,NULL);
pthread_join(pthid,(void**)&ival);
printf("retval=%d\n",ival);
return 0;
}
void* mainfunc1(void* arg)
{
for(int i=0;i<3;i++)
{
sleep(1);
printf("sleep %dsec ok\n",i);
}
//pthread_exit(0);
//return 0;
return (void*)10;
}
(6)线程取消
-
默认:
PTHREAD_CANCEL_ENABLE
状态,在主线程或其他线程中可调用pthread_cancel
取消该线程线程被取消后,主线程中通过
pthread_join
获取的线程的返回值为宏PTHREAD_CANCEL
即:-1pthread_cancel(pthread_t threadid);
-
子线程中调用
pthread_setcancelstate
设置pthread_cancel
有效与否pthread_cancel有效——
PTHREAD_CANCEL_ENABLE
pthread_cancel失效——
PTHREAD_CANCEL_DISABLE
pthread_setcancelstate(int state, int* oldstate); //state-要设置的状态 //oldstate-要保留之前的状态,不想保留就传NULL
-
子线程中调用
pthread_setcanceltype
设置线程取消的方式立即取消,调用
pthread_cancel
就立即取消——PTHREAD_CANCEL_ASYNCHRONOUS
延迟取消,线程直到代码执行到取消点才取消——
PTHREAD_CANCEL_DEFERRED
(意义不大,因为很多函数都是取消点) -
取消点
man pthreads 查看cancellation points
很多常用函数都是取消点,比如:
printf
()、sleep()
、fopen()
-
可通过
pthread_testcancel()
设置取消点#include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<pthread.h> void* mainfunc(void* arg); int main() { pthread_t pthid; if(pthread_create(&pthid,NULL,mainfunc,NULL)!=0) printf("create failed..\n"); usleep(100); pthread_cancel(pthid); //取消线程 int iret; int ival = pthread_join(pthid,(void**)&iret); //线程被取消,iret将为-1 printf("thread(%d) is exit,ret=%d\n",ival,iret); return 0; } int val=0; //跑到printf取消,能够执行printf打印 void* mainfunc(void* arg) { pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); for(int i=0;i<5000000;i++) { val++; } printf("val=%d\n",val); //printf是取消点,在这里就取消线程 pthread_testcancel(); pthread_exit(0); } //跑100ms就取消,不会执行到printf语句 void* mainfunc(void* arg) { pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); for(int i=0;i<5000000;i++) { val++; } printf("val=%d\n",val); pthread_testcancel(); pthread_exit(0); }
(7)线程清理
子线程退出时的善后工作,释放资源、回滚事务等。不适合放在主函数中,就放在清理函数中。
通过下面的函数,将清理函数压入、弹出堆栈:
pthread_cleanup_push(void(*routine)(void*),void* arg); //注册清理函数(注意第一个参数函数指针:返回值void)
pthread_cleanup_pop(int execute); //弹出并执行清理函数
- 上面2个函数必须成对写在代码块{}里,否则报错。
- 线程被取消时,按照压栈的相反顺序依次执行所有清理函数
- 线程被
pthread_exit()
终止时,也按照上述顺序依次执行所有清理函数 - 线程调用return返回时,不执行清理函数
- 传入非0参数到
pthread_cleanup_pop
中,将会弹出栈顶函数并执行 - 一般1个清理函数即可
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int ival=0;
void cleanfunc1(void* arg)
{
printf("cleanfunc1..\n");
}
void cleanfunc2(void* arg)
{
printf("cleanfunc2..\n");
}
void cleanfunc3(void* arg)
{
printf("cleanfunc3..\n");
}
void* func1(void* arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
pthread_cleanup_push(cleanfunc1,NULL);
pthread_cleanup_push(cleanfunc2,NULL);
pthread_cleanup_push(cleanfunc3,NULL);
for(int i=0;i<100000000;i++)
{
ival++;
printf("ival=%d\n",ival);
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_exit(0);
}
int main(int argc,char** argv)
{
pthread_t pthid;
pthread_create(&pthid, NULL, func1, NULL);
usleep(1000);
pthread_cancel(pthid);
pthread_join(pthid,NULL);
return 0;
}
(8)向线程发信号
和多进程中的信号有区别,多线程中用的比较少,也就用下
pthread_kill
-
外部(其他进程或bash)向进程发送信号,信号到达的是进程,会调用handler,但主进程、任一子线程都不会中断。
-
多线程程序中,信号捕获关联处理函数的
signal()
或sigaction()
的代码放在主线程、任一子线程都一样,都会改变所有线程的handler。一般放在主进程的函数中。
-
主进程向子线程发送信号:
若设置了handler则调用,若没设置handler则整个进程都会退出。
int pthread_kill(pthread_t threadid,int sig);
和外部发信号不同,这会中断指定的子线程当前执行的代码转而去执行信号处理函数handler,handler执行完后再继续执行子线程。
-
每个子线程调用
pthread_sigmask()
阻塞信号