目录
掌握:线程概念、线程特点、线程创建
线程概念
进程
线程
线程特点
使用多线程的好处

线程共享资源
一个进程中的多个线程共享以下资源:
线程私有资源
每个线程私有的资源包括:
linux内核中并没有实现线程,所以通过线程库实现
pthread线程库中提供了如下基本操作
同步和互斥机制
- #include
-
- int pthread_create(pthread_t *thread, const
- pthread_attr_t *attr, void *(*routine)(void *), void *arg);
示例:
- #include
- #include
- #include
-
- int *testThread(char *arg){
- printf("This is a thread test\n");
- return NULL;
- }
-
- int main(){
- pthread_t tid;
- int ret;
-
- ret = pthread_create(&tid,NULL,(void *)testThread,NULL);
-
- printf("This is main thread\n");
- sleep(1);
- }
-
- //如果不加printf和sleep1,主线程结束,子线程也结束可能就打印不出内容。
- linux@linux:~/Desktop$ gcc -o pthread pthread.c -lpthread
- linux@linux:~/Desktop$ ./pthread
- linux@linux:~/Desktop$ ./pthread
编译错误分析:
- createP_t.c:14:36: warning: passing argument 3 of ‘pthread_create’ from incompatible pointer type [-Wincompatible-pointer-types]
-
- ret = pthread_create(&tid,NULL,testThread,NULL);
-
- ^
-
- In file included from createP_t.c:1:0:
-
- /usr/include/pthread.h:233:12: note: expected ‘void * (*)(void *)’ but argument is of type ‘int * (*)(char *)’
意义:表示pthread_create参数3的定义和实际代码不符合,期望的是void * (*)(void *) ,实际的代码是int * (*)(char *)
解决方法:改为pthread_create(&tid,NULL,(void*)testThread,NULL);
- createP_t.c:(.text+0x4b):对‘pthread_create’未定义的引用
-
- collect2: error: ld returned 1 exit status --------这个链接错误,
表示pthread_create这个函数没有实现
解决方法:编译时候加 -lpthread
注意事项:1. 主进程的退出,它创建的线程也会退出。
线程创建需要时间,如果主进程马上退出,那线程不能得到执行
- #include
- void pthread_exit(void *retval);
示例
- #include
- #include
- #include
-
- void *testThread(void *arg){
- printf("This is a thread test\n");
- pthread_exit(NULL);
- printf("after pthread exit\n"); //不会被打印,线程已经清理
- }
- int main(){
- pthread_t tid;
- int ret;
- int arg = 5;
-
- ret = pthread_create(&tid,NULL,testThread,(void *)arg);
-
- printf("This is main thread\n");
- sleep(1);
- }
-
-
-
- //运行结果
- linux@linux:~/Desktop$ gcc -o pthread pthread.c -lpthread
- linux@linux:~/Desktop$ ./pthread
- This is main thread
- This is a thread test
- pthread_t pthread_self(void) 查看自己的TID
- #include
- pthread_t pthread_self(void);
示例:
- #include
- #include
- #include
-
- void *testThread(void *arg){
- printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
- // return NULL;
-
- pthread_exit(NULL);
- printf("after pthread exit\n");
- }
- int main(){
- pthread_t tid;
- int ret;
-
- ret = pthread_create(&tid,NULL,testThread,(void *)arg);
-
- printf("This is main thread,tid=%lu\n",tid);
- sleep(1);
- }
- pthread_create(pthread_t *thread, const
- pthread_attr_t *attr, void *(*routine)(void *), void *arg);
最后一个参数
示例:
- //方式1
- //直接传参数值也可以,把数当地址来传入了。因为int和指针都是4字节,如果是long就不行了
- #include
- #include
- #include
-
- void *testThread(void *arg){
- printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
- // return NULL;
- printf("input arg=%d\n",(int)arg);
- pthread_exit(NULL);
- printf("after pthread exit\n");
- }
- int main(){
- pthread_t tid;
- int ret;
- int arg = 5;
-
- ret = pthread_create(&tid,NULL,testThread,(void *)arg);
-
- printf("This is main thread,tid=%lu\n",tid);
- sleep(1);
- }
-
-
- //方式2
- #include
- #include
- #include
-
- void *testThread(void *arg){
- printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
- // return NULL;
- printf("input arg=%d\n",*(int *)arg);
- pthread_exit(NULL);
- printf("after pthread exit\n");
- }
- int main(){
- pthread_t tid;
- int ret;
- int arg = 5;
-
- ret = pthread_create(&tid,NULL,testThread,(void *)&arg);
-
- printf("This is main thread,tid=%lu\n",tid);
- sleep(1);
- }
-
补充:
编译错误:
- createP_t.c:8:34: warning: dereferencing ‘void *’ pointer
-
- printf("input arg=%d\n",(int)*arg);
-
- ^
-
- createP_t.c:8:5: error: invalid use of void expression
-
- printf("input arg=%d\n",(int)*arg);
错误原因是void *类型指针不能直接用*取值(*arg),因为编译不知道数据类型。
解决方法:转换为指定的指针类型后再用*取值 比如:*(int *)arg
示例:
- #include
- #include
- #include
-
- void *testThread(void *arg){
- printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
- // return NULL;
- printf("This is %d thread.\n", (int)arg);
- // pthread_exit(NULL);
- while(1){
- sleep(1);
- }
- printf("after pthread exit\n");
- }
- int main(){
- pthread_t tid[5];
- int ret;
- int arg = 5;
- int i;
- for(i=0;i<5;i++){
- ret = pthread_create(&tid[i],NULL,testThread,(void *)i);
-
- // sleep(1); //执行效率低无法打印i的值,因为传入的是地址,还没来得及改变,可以改用传值
- printf("This is main thread,tid=%lu\n",tid[i]);
- }
- while(1){
- sleep(1);
- }
- }
运行错误:
*** stack smashing detected ***: ./mthread_t terminated
已放弃 (核心已转储)
原因:栈被破坏了(数组越界)
使用pthread_create实现 10 个子线程,并且让每个子线程打印自己的线程号
- #include
- #include
- #include
-
- int *Thread_fun(char *arg)
- {
- printf("This is a thread test.pid=%d,tid=%lu,\n",getpid(),pthread_self());
- printf("This is %d thread.\n",(int)arg);
- while(1)
- {
- sleep(1);
- }
- printf("after pthread exit\n");
- return 0;
- }
-
-
- int main(int argc, char * argv[])
- {
- int i;
- pthread_t tid[10];
- int ret = 0;
-
- for(i = 0; i < 10; i++)
- {
- ret = pthread_create(&tid[i],NULL,(void *)Thread_fun,(void *)i);
- printf("This i main thread,tid=%lu\n",tid[i]);
- }
- while(1)
- {
- sleep(1);
- }
-
- }
ps -eLf
示例
- #include
- int pthread_join(pthread_t thread, void **retval);
对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放
注意:pthread_join 是阻塞函数,如果回收的线程没有结束,则一直等待
示例
- #include
- #include
- #include
-
- void *Thread_fun(void *arg)
- {
- printf("This is a child thread\n");
- sleep(1);
- pthread_exit("thread return");
- }
-
-
- int main(int argc, char * argv[])
- {
- pthread_t tid;
- void *retv;
-
- pthread_create(&tid,NULL,Thread_fun,NULL);
- pthread_join(tid,&retv);
- printf("thread ret=%s\n",(char *)retv);
- sleep(1);
-
- }
-
-
- //运行结果
- linux@linux:~/Desktop$ ./test_pthread
- This is a child thread
- thread ret=thread return
多个线程回收
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- sleep(25);
- pthread_exit("thread return");
-
- }
-
-
- int main(){
- pthread_t tid[100];
- void *retv;
- int i;
- for(i=0;i<100;i++){
- pthread_create(&tid[i],NULL,func,NULL);
- }
- for(i=0;i<100;i++){
- pthread_join(tid[i],&retv);
- printf("thread ret=%s\n",(char*)retv);
- }
- while(1){
- sleep(1);
- }
-
- }
回收效果,使用top命令
- linux@linux:~$ ps -ef|grep "pjoin"
- linux 16330 2072 0 10:46 ? 00:00:01 gedit /home/linux/Desktop/pjoin.c
- linux 16467 2732 0 10:50 pts/9 00:00:00 ./pjoin
- linux 16573 15358 0 10:51 pts/1 00:00:00 grep --color=auto pjoin
- linux@linux:~$ top -p 16467
发现回收前后,虚拟内存减小,实际内存减小,如果不使用回收,内存不会有变化还可能变大。


使用线程的分离两种方式:
1 使用pthread_detach
2 创建线程时候设置为分离属性
int pthread_detach(pthread_t thread);
成功:0;失败:错误号
指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程)
此种方式不需要通过主线程取回收线程资源。
示例
- #include
- #include
- #include
- void *func(void *arg){
- pthread_detach(pthread_self()); //方式2
- printf("This is child thread\n");
- sleep(25);
- pthread_exit("thread return");
-
- }
-
-
- int main(){
- pthread_t tid[100];
- void *retv;
- int i;
- for(i=0;i<100;i++){
- pthread_create(&tid[i],NULL,func,NULL);
- //pthread_detach(tid); //方式1
- }
-
- while(1){
- sleep(1);
- }
-
- }
实际效果:同2.6回收效果,虚拟内存减小,实际内存减小,如果不使用detach,内存不会有变化还可能变大。
pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
设置线程属性为分离
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
示例:
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- sleep(25);
- pthread_exit("thread return");
-
- }
-
-
- int main(){
- pthread_t tid[100];
- void *retv;
- int i;
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //分离属性
-
- for(i=0;i<100;i++){
- pthread_create(&tid[i],&attr,func,NULL);
- // pthread_detach(tid);
- }
-
- while(1){
- sleep(1);
- }
-
- }
线程分离的目的:
资源回收:当线程被标记为可分离(detached)时,线程退出后,系统会自动回收其占用的资源,无需其他线程等待或执行特定的回收操作。这对于长时间运行或创建大量线程的应用程序很有用,因为它可以减少资源泄漏的风险。
线程管理:标记线程为可分离使得其独立于主线程或其他线程而存在,它可以自主地运行和结束,不会影响其他线程的正常执行。这对于一些需要并发执行的任务或周期性任务非常有用,可以提高整体的系统性能和响应能力。
需要注意的是,线程创建时的默认属性是非分离(joinable),即需要使用 pthread_join 函数来等待线程的结束并回收资源。如果需要将线程设置为可分离属性,可以使用 pthread_attr_setdetachstate 函数将属性设置为 PTHREAD_CREATE_DETACHED。
int pthread_cancel(pthread_t thread); 杀死一个线程
作用:如果一个线程是个死循环,那么exit可能永远也执行不到。这种情况需要取消线程的功能。
意义:随时杀掉一个线程,如果使用kill命令,会把进程也一起杀掉。
注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用
示例:
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- while(1)
- {
- sleep(5);
-
- }
- pthread_exit("thread return"); //永远不会执行
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(5);
- pthread_cancel(tid);
- pthread_join(tid,&retv);
- printf("thread ret=%s\n",(char*)retv); //这里会出现段错误,这是空指针
- while(1){
- sleep(1);
- }
-
- }
运行段错误调试:
可以使用gdb调试
使用gdb 运行代码,gdb ./youapp
- (gdb) run
-
- 等待出现Thread 1 "pcancel" received signal SIGSEGV, Segmentation fault.
-
- 输入命令bt(打印调用栈)
-
- (gdb) bt
-
- #0 0x00007ffff783ecd0 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6 //vfprintf报错
- #1 0x00007ffff78458a9 in printf () from /lib/x86_64-linux-gnu/libc.so.6 //#2调用了#1
- #2 0x00000000004007f9 in main () at pcancel.c:21 //栈底
- 确定段错误位置是pcancel.c 21行
注释掉后调试结果
- linux@linux:~$ ps -eLf|grep "pcancel"
- linux 3467 2962 3467 0 2 02:50 pts/7 00:00:00 ./pcancel
- linux 3467 2962 3468 0 2 02:50 pts/7 00:00:00 ./pcancel
- linux 3470 3184 3470 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
- linux@linux:~$ ps -eLf|grep "pcancel"
- linux 3467 2962 3467 0 2 02:50 pts/7 00:00:00 ./pcancel
- linux 3467 2962 3468 0 2 02:50 pts/7 00:00:00 ./pcancel
- linux 3472 3184 3472 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
- linux@linux:~$ ps -eLf|grep "pcancel"
- linux 3467 2962 3467 0 1 02:50 pts/7 00:00:00 ./pcancel
- linux 3474 3184 3474 0 1 02:50 pts/2 00:00:00 grep --color=auto pcancel
- linux@linux:~$
如果没有取消点,手动设置一个
void pthread_testcancel(void);
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- while(1)
- {
- pthread_testcancel(); //如果程序代码很长,找不到死循环的位置,可以用这段代码
- }
- pthread_exit("thread return"); //永远不会执行
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(5);
- pthread_cancel(tid);
-
- while(1){
- sleep(1);
- }
-
- }
-
如果没有取消点,手动设置一个
目的:让有些代码可以被取消,有些代码不能被取消。有些先后顺序不好找的时候。
- int pthread_setcancelstate(int state, int *oldstate);
- PTHREAD_CANCEL_ENABLE
- PTHREAD_CANCEL_DISABLE
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); //不能被取消,不管有没有阻塞点
- while(1)
- {
- sleep(5);
- pthread_testcancel();
- }
- pthread_exit("thread return"); //永远不会执行
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(1);
- pthread_cancel(tid);
- pthread_join(tid,&retv);
- // printf("thread ret=%s\n",(char*)retv);
- while(1){
- sleep(1);
- }
-
- }
-
-
- //运行结果
- linux@linux:~$ ps -eLf|grep "pcancel"
- linux 6271 2962 6271 0 2 03:43 pts/7 00:00:00 ./pcancel
- linux 6271 2962 6272 0 2 03:43 pts/7 00:00:00 ./pcancel
- linux 6287 3184 6287 0 1 03:46 pts/2 00:00:00 grep --color=auto pcancel
-
此时在设置可以取消。
- #include
- #include
- #include
- void *func(void *arg){
- printf("This is child thread\n");
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
- sleep(5);
- pthread_testcancel(); //如果程序代码很长,找不到死循环的位置,可以用这段代码
- pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); //可以取消了
- pthread_exit("thread return"); //永远不会执行
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(5); //5秒内不能被取消,可以执行完上述代码
- pthread_cancel(tid);
-
- while(1){
- sleep(1);
- }
-
- }
总结:
能不能取消却决于有没有取消点,pthread_testcancel(),同时设置是否能被取消。
如果不能被取消,则有没有取消点都没有关系了。
- int pthread_setcanceltype(int type, int *oldtype);
- PTHREAD_CANCEL_DEFERRED 等到取消点才取消
- PTHREAD_CANCEL_ASYNCHRONOUS 目标线程会立刻取消
问题:如果线程取消了,代码没有正常退出,内存没有释放,会造成内存泄漏怎么解决?
必要性:当线程非正常终止,需要清理一些资源。
- void pthread_cleanup_push(void (*routine) (void *), void *arg)
- void pthread_cleanup_pop(int execute)
两个函数需要成对出现再代码中,否则会出错:
- #include
- #include
-
- void cleanup(void *arg)
- {
- printf("cleanup=%s\n",(char*)arg);
- }
-
- void *func(void *arg)
- {
- printf("This is child thread\n");
- pthread_cleanup_push(cleanup,"abcd");
- pthread_exit("thread return");
- }
-
-
- int main()
- {
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(1);
- pthread_cancel(tid);
- pthread_join(tid,&revt);
-
- while(1)
- {
- sleep(1);
- }
-
- }
-
- linux@linux:~/Desktop$ gcc -g -o pcancel pcancel.c -lpthread
- pcancel.c: In function ‘func’:
- pcancel.c:18:1: error: expected ‘while’ before ‘int’
- int main()
- ^
- pcancel.c:33:1: error: expected declaration or statement at end of input
- }
- ^
- pcancel.c:33:1: error: expected declaration or statement at end of input
查看vim /usr/include/pthread.h

正确用法:
- #include
- #include
- #include
-
- void cleanup(void *arg)
- {
- //做清理工作的代码
- printf("cleanup=%s\n",(char*)arg);
- }
-
- void *func(void *arg)
- {
- printf("This is child thread\n");
- pthread_cleanup_push(cleanup,"abcd");
- pthread_exit("thread return"); //执行这句话,上面cleanup也会被调用到。
- pthread_cleanup_pop(0); //此时这句话意义在于完成大括号结束,0代表删除函数
-
- }
-
- /*
- void *func(void *arg)
- {
- printf("This is child thread\n");
- pthread_cleanup_push(cleanup,"abcd");
- pthread_cleanup_pop(1); // 非0参数执行pthread_cleanup_pop()
- pthread_exit("thread return");
-
- }
- */
-
- int main()
- {
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(1);
- pthread_cancel(tid);
- pthread_join(tid,&revt);
-
- while(1)
- {
- sleep(1);
- }
-
- }
-
- //运行结果
- linux@linux:~/Desktop$ ./pcancel
- This is child thread
- cleanup=abcd
-
routine 函数被执行的条件:
注意:
- #include
- #include
- #include
-
- void cleanup(void *arg){
- printf("cleanup,arg=%s\n",(char*)arg);
-
- }
- void cleanup2(void* arg){
-
- printf("cleanup2,arg=%s\n",(char*)arg);
- }
-
- void *func(void *arg){
- printf("This is child thread\n");
- pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); //设置了遇到取消点立刻取消线程
- pthread_cleanup_push(cleanup,"abcd");
- pthread_cleanup_push(cleanup2,"efgh");
- //while(1)
- {
- sleep(1);
-
- }
- pthread_cancel(pthread_self()); //取消线程,也会触发回调
- printf("Should not print\n");
-
- while(1){
- printf("sleep\n");
- sleep(1);
- }
- pthread_exit("thread return");
- pthread_cleanup_pop(1);
- pthread_cleanup_pop(1);
- sleep(10);
- pthread_exit("thread return");
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(1);
- // pthread_cancel(tid);
- pthread_join(tid,&retv);
- //printf("thread ret=%s\n",(char*)retv);
- while(1){
- sleep(1);
- }
-
- }
-
- //运行结果
- linux@linux:~/Desktop$ ./pcancel
- This is child thread
- cleanup2,arg=efgh
- cleanup,arg=abcd
如改变一下:return和 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。
- #include
- #include
- #include
-
- void cleanup(void *arg){
- printf("cleanup,arg=%s\n",(char*)arg);
-
- }
- void cleanup2(void* arg){
-
- printf("cleanup2,arg=%s\n",(char*)arg);
- }
-
- void *func(void *arg){
- printf("This is child thread\n");
- pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
- pthread_cleanup_push(cleanup,"abcd");
- pthread_cleanup_push(cleanup2,"efgh");
- //while(1)
- {
- sleep(1);
-
- }
- // pthread_cancel(pthread_self());
- //printf("Should not print\n");
- return "1234";
-
- while(1){
- printf("sleep\n");
- sleep(1);
- }
- pthread_exit("thread return");
- pthread_cleanup_pop(1);
- pthread_cleanup_pop(1);
- sleep(10);
- pthread_exit("thread return");
- }
-
-
- int main(){
- pthread_t tid;
- void *retv;
- int i;
- pthread_create(&tid,NULL,func,NULL);
- sleep(1);
- // pthread_cancel(tid);
- pthread_join(tid,&retv);
- printf("thread ret=%s\n",(char*)retv);
- while(1){
- sleep(1);
- }
-
- }
练习
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute) 的本质是什么?D
A. 两个函数
B. pthread_cleanup_push函数可以取消一个线程
C. pthread_cleanup_pop函数可以清理一个线程
D. 两个宏定义,必须配对使用