小游戏网站,家装设计师培训班多少钱一个月,电商平台怎么运营的,安康免费做网站公司在Linux C开发环境中#xff0c;通常有两种方式来开发多线程程序#xff0c;一种是利用POSIX多线程 API函数来开发多线程程序#xff0c;另外一种是利用C自带线程类来开发程序。
常见的与线程相关的基本API函数#xff1a;
API函数含义pthread_create创建线程pthread_exi…在Linux C开发环境中通常有两种方式来开发多线程程序一种是利用POSIX多线程 API函数来开发多线程程序另外一种是利用C自带线程类来开发程序。
常见的与线程相关的基本API函数
API函数含义pthread_create创建线程pthread_exit线程终止自身执行pthread_join等待一个线程的结束pthread_self获取线程IDpthread_cancel取消另外一个线程pthread_kill向线程发送一个信号
pthread_join是个阻塞函数函数pthread_join会让主线程挂起即休眠让出CPU直到子线程都退出同时pthread_join能让子线程所占的资源得到释放。子线程退出以主线程会接收到系统的信号从休眠中恢复。
线程的创建
下面举一个简单的创建线程的例子新建一个createThreadTest.cpp 文件
例子1. 创建一个简单的线程不传参数。
#include pthread.h
#include stdio.h
#include unistd.hvoid *thfunc(void *arg){printf(in thfunc\n);return (void*)0;
}int main(int argc, char *argv[]){pthread_t tidp;int ret;ret pthread_create(tidp,NULL,thfunc,NULL); //创建线程if(ret){printf(pthread_create failed:%d\n,ret);return -1;}sleep(1); //main线程挂起1秒钟为了让子线程有机会执行printf(in main:thread is created\n);return 0;
}在终端执行
g -o createThreadTest createThreadTest.cpp -lpthread
./createThreadTest.cpp 输出
in thfunc
in main:thread is created在这个例子中首先创建一个线程线程函数在打印一行字符串后结束而主线程在创建子线程后会等待一秒钟避免主线程的过早结束而导致进程结束。如果没有等待函数sleep则可能子线程的线程函数还没来得及执行主线程就结束了。
例子2.创建一个线程并传入整形参数。
#include pthread.h
#include stdio.h
#include unistd.hvoid *thfunc(void *arg){int *pn (int*)(arg);int n *pn;printf(in thfunc n %d\n, n);return (void*)0;
}int main(int argc, char *argv[]){pthread_t tidp;int ret, n 110;ret pthread_create(tidp,NULL,thfunc,n); //创建线程if(ret){printf(pthread_create failed:%d\n,ret);return -1;}pthread_join(tidp,nullptr);printf(in main:thread is created\n);return 0;
}终端执行
g -o createThreadTest2 createThreadTest2.cpp -lpthread输出如下
in thfunc n 110
in main:thread is created例2和例1有两点不同一是创建线程的时候把一个整形变量的地址作为参数传给线程函数二是等待子线程结束没有用sleep函数而用pthread_join。sleep只是等待一个固定的时间有可能在这个固定的时间内子线程早已经结束或者子线程运行的时间大于这个固定时间因此用它来等待子线程结束并不精确而用thread_join则会一直等到子线程结束后才会执行该函数后面的代码我们可以看到它的第一个参数是子线程的ID。
例子3创建一个线程并传递字符串作为参数。
#include pthread.h
#include stdio.h
#include unistd.hvoid *thfunc(void *arg){char *str;str (char *)arg;printf(in thfunc str %s\n, str);return (void*)0;
}int main(int argc, char *argv[]){pthread_t tidp;int ret;const char *str hello world;ret pthread_create(tidp,NULL,thfunc,(void *)str); //创建线程if(ret){printf(pthread_create failed:%d\n,ret);return -1;}pthread_join(tidp,nullptr);printf(in main:thread is created\n);return 0;
}wjrDESKTOP-UMB8379:~/compuThink$ g -o createThreadTest3 createThreadTest3.cpp -lpthread
wjrDESKTOP-UMB8379:~/compuThink$ ./createThreadTest3
in thfunc str hello world
in main:thread is created例4.创建一个线程并传递结构体作为参数。
#include pthread.h
#include stdio.h
#include unistd.htypedef struct{ //定义结构体的类型int n;char *str;
}MYSTRUCT;void *thfunc(void *arg){MYSTRUCT *p (MYSTRUCT*)arg;printf(in thfunc:n %d, str %s\n, p-n,p-str);return (void*)0;
}int main(int argc, char *argv[]){pthread_t tidp;int ret;MYSTRUCT mystruct;mystruct.n 110;mystruct.str hello world;ret pthread_create(tidp,NULL,thfunc,(void *)mystruct); //创建线程if(ret){printf(pthread_create failed:%d\n,ret);return -1;}pthread_join(tidp,nullptr);printf(in main:thread is created\n);return 0;
}wjrDESKTOP-UMB8379:~/compuThink$ g -o createThreadTest4 createThreadTest4.cpp -lpthread
createThreadTest4.cpp: In function ‘int main(int, char**)’:
createThreadTest4.cpp:21:20: warning: ISO C forbids converting a string constant to ‘char*’ [-Wwrite-strings]21 | mystruct.str hello world;| ^~~~~~~~~~~~~
wjrDESKTOP-UMB8379:~/compuThink$ ./createThreadTest4
in thfunc:n 110, str hello world
in main:thread is created例5.创建一个线程共享进程数据
#include pthread.h
#include stdio.h
#include unistd.hint gn 10; //定义一个全局变量将会在主线程和子线程中用到void *thfunc(void *arg){gn; //递增1printf(in thfunc gn %d\n, gn);return (void*)0;
}int main(int argc, char *argv[]){pthread_t tidp;int ret;ret pthread_create(tidp,NULL,thfunc,NULL); //创建线程if(ret){printf(pthread_create failed:%d\n,ret);return -1;}pthread_join(tidp,nullptr); //等待子线程结束gn; //子线程结束后gn再递增1printf(in main gn%d\n,gn); //再次打印全局变量gn的值return 0;
}wjrDESKTOP-UMB8379:~/compuThink$ g -o createThreadTest5 createThreadTest5.cpp -lpthread
wjrDESKTOP-UMB8379:~/compuThink$ ./createThreadTest5
in thfunc gn 11
in main gn12从此例中可以看到全局变量gn首先在子线程中递增1等子线程结束后再在主线程中递增1。两个线程都对同一个全局变量进行了访问。
###线程的属性 POSIX标准规定线程具有多个属性。线程的主要属性包括分离状态Detached State、调度策略和参数Scheduling Policy and Parameters、作用域Scope、栈尺寸Stack Size、栈地址Stack Address、优先级priority等。Linux为线程属性定义一个联合体pthread_attr_t,注意是联合体而不是结构体定义的地方在/usr/include/bits/pthreadtypes.h中定义如下
union pthread_attr_t
{char __size[__SIZEOF_PTHREAD_ATTR_T];long int __align;
}从这个定义中可以看出属性值都是存放在数组_size中的不方便存取。但在Linux中有一组专门用于存取属性值的函数。如果获取线程的属性首先要用函数pthread_getattr_np来获取属性结构体值再用相应的函数来获取某个属性具体值。函数pthread_getattr_np声明如下
int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);其中参数thread是线程IDattr返回线程属性结构体的内容。如果函数成功返回0.否则返回错误码。注意使用该函数需要在pthread.h钱定义宏_GNU_SOURCE,代码如下
#define _GNU_SOURCE
#include pthread.h并且当函数pthread_getattr_np获得的属性结构体变量不再需要的时候应该用函数pthread_attr_destroy进行销毁。 我们前面用pthread_create创建线程的时候属性结构体指针参数用了NULL此处创建的线程具有默认属性即为非分离大小为1MB的堆栈与父进程具有同样级别的优先级。如果要创建非默认属性的线程可以在创建线程之前用函数pthread_attr_init来初始化一个线程属性结构体再调用相应的API函数来设置相应的属性。接着把属性结构体的指针作为参数传入pthread_create。函数pthread_attr_init声明如下
int pthread_attr_init(pthread_attr_t *attr);其中参数attr为指向线程属性结构体的指针。如果函数成功返回0.否则返回一个错误码。 需要注意的是使用pthread_attr_init初始化线程属性使用完即传入pthread_create后需要使用pthread_attr_destroy进行销毁从而释放相应资源。函数pthread_attr_destroy声明如下
int pthread_attr_destroy(pthread_attr_t *attr);1.分离状态
分离状态是线程的一个重要属性。POSIX线程的分离状态决定了一个线程以什么样的方式终止。默认的分离状态是可连接即创建线程时如果使用默认属性则分离状态属性就是可连接。 POSIX的线程要么是分离状态的要么是非分离状态的也称为可连接的,joinable。前者用宏PTHREAD_CREATE_DETACHED表示后者用宏PTHREAD_CREATE_JOINABLE表示。
默认情况下创建的线程是可连接的。
一个可连接的线程可以被其他线程收回资源和取消并且它不会主动释放资源比如栈空间必须等待其他线程来回收其资源因此我们要在主线程使用pthread_join该函数是个阻塞函数当它返回时所等待的线程的资源也释放了。 再次强调如果是可连接线程当线程函数自己返回结束时或调用pthread_exit结束时都不会释放线程所占用的堆栈和线程描述符总计8K多必须自己调用pthread_join且返回后这些资源才会被释放。
这对于父进程长时间运行的线程来说其结果会是灾难性的。因为父进程不退出并且没有调用pthread_join则这些可连接线程的资源就一直不会释放相当于僵尸线程了僵尸线程越来越多以后再想创建新线程将会变得没有资源可用。 如果不用pthread_join,即使父进程先于可连接子线程退出也不会泄露资源。
如果父进程先于子线程退出那么它将被init进程所收养这个时候init就是它的父进程它将调用wait系列函数为其回收资源因此不会泄露资源。
总之一个可连接的线程所占用的内容仅当有线程对其执行pthread_join才会释放因此为了避免内存泄露可连接的线程在终止时要么已被设为DETACHED可分离要么使用pthread_join来回收其资源。另外一个线程不能被多个线程等待否则第一个受到信号的线程成功返回其中调用pthread_join的线程将得到错误代码ESRCH。
了解了可连接线程我们来看可分离的线程这种线程运行结束时其资源将立即被系统回收。可以这样理解这种线程能独立分离出去可以自生自灭父线程不用管它了。将一个线程设置为可分离状态有两个方式一种是调用函数pthread_detach它可以将线程转换为可分离线程另一种是在创建线程的时候将它设置为可分离状态。
基本过程是首先初始化一个线程属性的结构体变量通过函数pthread_attr_init然后将其设置为可分离状态通过函数pthread_attr_setdetachstate最后将该结构体变量的地址作为参数传入线程创建函数pthread_create这样创建出来的线程就直接处于可分离状态。
函数pthread_attr_setdetachsate用于设置线程的可分离状态属性声明如下
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);例6.创建一个可分离线程。
#include iostream
#include pthread.h
#include unistd.husing namespace std;void *thfunc(void *arg){cout (sub thread is runnning\n);return NULL;
}int main(int argc, char *argv[]){pthread_t thread_id;pthread_attr_t thread_attr;struct sched_param thread_param;size_t stack_size;int res;res pthread_attr_init(thread_attr);if(res)cout pthread_attr_init failed: res endl;res pthread_attr_setdetachstate(thread_attr,PTHREAD_CREATE_DETACHED);if(res)cout pthread_attr_setdetachstate failed: res endl;res pthread_create(thread_id, thread_attr, thfunc, NULL);if(res)cout pthread_create failed: res endl;cout main thread will exit\n endl;sleep(1);return 0;
}wjrDESKTOP-UMB8379:~/compuThink$ g -o detachTest1 detachTest1.cpp -lpthread
wjrDESKTOP-UMB8379:~/compuThink$ ./detachTest1
main thread will exit
sub thread is runnning
在上面代码中我们首先初始化了一个线程属性结构体然后设置其分离状态为PTHREAD_CREATE_DETACHED并用这个属性结构体作为参数传入线程创建函数中。这样创建出来的线程就是可分离线程。这意味着该线程结束时它所占用的任何资源都可以立即让系统回收。程序的最后我们让主线程挂起一秒让子线程有机会执行。因为如果主线程很早就退出将会导致整个进程很早退出子线程就没有机会执行了。
如果子线程执行的时间长则sleep的设置比较麻烦。有一种机制不用sleep函数即可让函数完整执行。对于可连接线程主线程可以用pthread_join函数等待子线程结束。而对于可分离线程并没有这样的函数但可以采用这样的方法 先让主线程退出而进程不退出一直等待子线程退出了主线程才退出即在主线程中调用pthread_exit在主线程如果调用了pthread_exit那么终止的只是主线程而进程的资源会为主线程创建的其他线程保持打开的状态直到其他线程都终止。 值得注意的是如果在非主线程即其他子线程中调用pthread_exit则不会有这样的效果只会退出当前子线程。下面不用sleep函数重新改写例6.
例7创建一个可分离线程且主线程先退出
#include iostream
#include pthread.h
#include unistd.husing namespace std;void *thfunc(void *arg){cout (sub thread is runnning\n);return NULL;
}int main(int argc, char *argv[]){pthread_t thread_id;pthread_attr_t thread_attr;struct sched_param thread_param;size_t stack_size;int res;res pthread_attr_init(thread_attr);if(res)cout pthread_attr_init failed: res endl;res pthread_attr_setdetachstate(thread_attr,PTHREAD_CREATE_DETACHED);if(res)cout pthread_attr_setdetachstate failed: res endl;res pthread_create(thread_id, thread_attr, thfunc, NULL);if(res)cout pthread_create failed: res endl;cout main thread will exit\n endl;pthread_exit(NULL); //主线程先退出但进程不会此刻退出下面的语句不会执行cout main thread has exited, this line will not run\n endl;return 0;
}wjrDESKTOP-UMB8379:~/compuThink$ g -o detachTest2 detachTest2.cpp -lpthread
wjrDESKTOP-UMB8379:~/compuThink$ ./detachTest2
sub thread is runnning
main thread will exit例8获取线程的分离状态属性