谷歌的网站打不开,软件开发行业发展前景,wordpress企业主题下载地址,无极电影网迅雷下载了解了线程的基本原理之后#xff0c;我们来学习线程在C语言官方库中的写法与用法。
1. 常见pthread接口及其背后逻辑
1.1 pthread_create 与线程有关的函数构成了⼀个完整的系列#xff0c;绝⼤多数函数的名字都是以“pthread_”打头的 • 要使⽤这些函数库#xff0c;…了解了线程的基本原理之后我们来学习线程在C语言官方库中的写法与用法。
1. 常见pthread接口及其背后逻辑
1.1 pthread_create 与线程有关的函数构成了⼀个完整的系列绝⼤多数函数的名字都是以“pthread_”打头的 • 要使⽤这些函数库 要通过引⼊头文件 pthread.h • 链接这些线程函数库时要使⽤编译器命令的“-l pthread”选项 pthread_create 函数并不是Linux提供的创建线程的系统调用而是Linux中对轻量级进程已有的接口进行封装的动态库中的函数 #include iostream
#include pthread.h
#include unistd.husing namespace std;void *routine(void *arg)
{//值得注意的是线程执行的函数的返回值一定是一个void*用于返回一个指针-》这样的目的是让所有类型的参数都能被返回//包括但不限于类对象函数容器等等。//而参数void*是一种泛型编程的实现方式这样一来可以根据传入的内容来指定完成任务。while (true){cout new thread endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(tid, nullptr, run, (void *)thread-1);while (true){cout main thread endl;sleep(1);}return 0;
} 由实验结果可知main和new thread的执行顺序并不固定。 两个线程是异步打印使用到同一个文件资源显示器上所以存在打印错乱的情况但是的确可以做到每一个线程完成自己的任务而不会受到其他线程的干扰这两个线程就是两个不同的执行流而因为routine函数始终在访问同一个资源所以此时其是不可重入函数。 pthread_create的底层其实是clone-------一个非常复杂不便于使用的函数。 由于Linux在内核代码中并没有将进程和线程完全分成两个系统管理所以clone的flag参数就有以下两种选择创建一个parent进程或者是只创建一个轻量级进程clone_thread 创建进程还是创建线程一个文字游戏而已。 了解clone第一个参数即传给该线程的函数第二个是栈空间那么多虚拟内存的段为什么要单独传一个这最后一个是线程要执行的函数的参数。。。。。。。 所以现在就可以进一步理解用户级线程 pthread_create调用的是cloneclone其实也是被封装的底层还有如do_clone等函数。 最后关于pthread_create的返回值 pthreads函数出错时不会设置全局变量errno⽽⼤部分其他POSIX函数会这样做。⽽是将错 误代码通过返回值返回。没有出错返回0出错返回错误码。 对于pthreads函数的错误建议通过返回值判定因为读取返回值要⽐读取线程内的errno变量的开销更小。strerror和errno都是之前介绍进程的时候提及的概念 1.2 pthread_self pthread_self用于查看任意线程的线程ID 在上一文中我们提到以下指令用于查看 ps -aL | head -1 ps -aL | grep thread 或者 while :; do ps ajx |head -1 ps ajx | grep test | grep -v grep;sleep 1;echo------------------------------------------------;done 这个线程的tid值似乎与之前提到的lwp不太一样呢 请记住这个伏笔会在之后的内容中重点聊。 如果觉得这个数字太长我们可以
string ToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),0x%lx,tid);return buffer;
}
这个tid似乎看着很像地址 多线程状态对于资源的使用 能够使用以上两个接口我们便能创建一些多线程的demo。 下面我们来验证一下多线程的栈区是否是独立的 //.....................
void *run(void *arg)
{//值得注意的是线程执行的函数的返回值一定是一个void*用于返回一个指针-》这样的目的是让所有类型的参数都能被返回//包括但不限于类对象函数容器等等。//而参数void*是一种泛型编程的实现方式这样一来可以根据传入的内容来指定完成任务。string name static_castconst char*(arg);int val 0;while (true){cout new thread,name : name my_threadID : ToHex(pthread_self())my val : valendl;val;if(namethread-1) val;sleep(1);}
}int main()
{pthread_t tid1;pthread_t tid2;pthread_create(tid1, nullptr, run, (void *)thread-1);pthread_create(tid2, nullptr, run, (void *)thread-2);
//........... 实验现象也不出所料两个栈的val值互不影响。 两个新线程都在修改val但是二者并没有影响到另外的val这也验证了每个线程实际上也有自己的栈。 再尝试让两个线程都修改同一个全局变量 果然两个线程在某种意义上已经形成了“通信”。 将val变量作为了全局变量供两个线程进行访问此时两个线程均看得到这个变量所以这个变量就是两个线程的共享资源与前面显示器打印问题一样因为共用并且没加保护导致一个线程的修改会影响到另一个线程这个效果同样适用于静态变量 两个线程谁先执行不一定并且不同的线程会瓜分时间片 其实独立的线程不代表是私有的线程强行使用地址去访问某一个线程栈的内容依然能访问到。 结果 但是这样的做法是非常不推荐的是错误的。也在一定的程度上的说明只要拿到虚拟地址就能访问一切的“不允许”都是被规则规定的。 线程局部存储 如果希望一个变量被每一个线程都单独创建副本则使用__thread来修饰该变量。该关键词只能用于修饰内置类型。 栈的特性与存储 栈是每个线程私有的内存区域用于存储局部变量、方法参数和返回地址等。每个新线程都会创建自己的栈。栈上的数据独立于其他线程这意味着如果一个线程在其栈中创建了一个变量其他线程不能直接访问该变量除非通过方法参数或共享对象显式传递。这种独立性提供了天然的线程安全因为每个线程都有自己的栈空间不会无意间影响其他线程的数据。 堆的特性与存储 堆是一个全局共享的内存区域用于动态分配内存。所有线程都可以访问堆上分配的对象。如果一个对象在堆上被创建并且其引用内存地址被传递给多个线程那么这些线程就可以同时访问甚至修改该对象的内容。由于堆是共享的所以在多线程环境下访问堆上的对象时需要特别小心通常需要使用同步机制来避免竞态条件或数据不一致的问题。 总之不论是堆上的数据还是栈上的数据只要能获取到指定内容的地址就不存在所谓的栈独立性此刻栈和堆都是共享的而所谓的栈独立性和堆共享性更强调所有的线程都有自己的栈但是堆共有一个 1.3 pthread_join 已经退出的线程其空间没有被释放仍然在进程的地址空间内。 创建新的线程不会也不能复⽤刚才退出线程的地址空间。 为了解决类似于进程中“僵尸进程”的问题并且获得线程的任务执行情况。被主线程创建的线程一般需要被等待并且回收。 参数 : thread: 线程 ID希望被join掉也就是希望被终止的线程ID value_ptr: 它指向⼀个指针双重指针是线程执行的函数的返回值的地址。 返回值成功返回 0 失败返回错误码。 比如我们现在不需要线程执行的任务的返回值 pthread_join的第二个参数是一个输出型参数因为线程执行的函数是一个void*类型的返回值为了让这样的void*参数能够自动适应所有的返回值所以必须使用一个void**的参数 线程执行的任务想返回一个10所以就用void*强转一下10ret是一个void*用于接受这个10的值所以10必须强转成void*。 void*是8个字节现在的linux机器或者其他机器几乎都是64位此时10被当成了一个void* 所以用int去强转会出现报错应该采用long long去强转。 或者用一个堆指针来完成这个任务也可以先把void*的指针变成int*再解引用。 一个线程只要return就退出了 一个void**类型的变量ret去记录线程执行的函数的返回值return的值 错误使用 这么做虽然能正确找到这个ret值但是是不科学的因为a是一个栈上的变量按理来说这个变量会销毁。更建议使用堆指针。 以上代码说明说明堆空间是共享的。线程之间能拿到其他线程创建的堆资源不过后续需要自己销毁资源 另外直接pthread_join自己会报错 既然pthread_create的回调函数支持传入任何参数那么假设我们希望给每一个线程传一个结构体并且打印、操作该结构体对应的数据。
一个简单的传结构体指针demo
class ThreadData
{
public:ThreadData(const string name, int a, int b): _name(name), _dataA(a), _dataB(b){}pairint, int GetInfo(){return pairint, int(_dataA, _dataB);}const int GetA(){return _dataA;}void Exec(){_dataA;_dataB * 10;cout _name endl;}~ThreadData() {}private:string _name;int _dataA;int _dataB;
};void *routine(void *th_data)
{ThreadData *p_data (ThreadData *)th_data;//while (true)//{cout new thread,myID: ToHex(pthread_self()) A:B p_data-GetInfo().first : p_data-GetInfo().second endl;p_data-Exec();cout new thread,myID: ToHex(pthread_self()) A:B p_data-GetInfo().first : p_data-GetInfo().second endl;sleep(1);//}return (void*)p_data;
}int main()
{pthread_t tid;ThreadData *th_p new ThreadData(thread-1, 10, 20);int err_n1 pthread_create(tid,nullptr,routine,(void*)th_p);if(err_n1!0){cerrcreate error strerror(err_n1)endl;return 1;}else {coutcreate successendl;}void* ret nullptr;int err_n2 pthread_join(tid,ret);if (err_n2 ! 0){cerr join error strerror(err_n2) endl;}elsecout joined success! ret num A : ((ThreadData*)ret)-GetA() endl;
}
创建多个线程的demo 之前的ThreaData就是为了在一个新的线程的时候有对应的数据和方法 pthread_join在进行的其实也是一种阻塞等待这会影响主线程的工作效率。 1.4 线程分离pthread_detach 默认情况下新创建的线程是joinable的线程退出后需要对其进⾏pthread_join操作否则⽆法释放资源从⽽造成系统泄漏。 • 如果不关⼼线程的返回值join是⼀种负担这个时候我们可以告诉系统当线程退出时⾃动释放线程资源 join和detach是矛盾的。 需要注意的是在多执行流的情况下一定要确保主执行流最后退出防止主执行流先退出导致 分离的线程 需要的资源被释放导致错误。线程可以自主分离在线程的执行函数中执行pthread_detach(pthread_self(); 或者被迫分离-》主线程等其他线程调用该函数。 线程程序替换 注意进程程序替换是不可以直接发生在线程执行流中的因为程序替换的本质是替换掉当前进程的代码此时线程的代码也会被替换从而导致错误但是可以在线程中创建子进程再在子进程中使用进程程序替换 1.5 线程退出
首先不能直接用exit()因为exit()会直接让整个进程都退出。 pthread_exit()pthread_exit的参数也会被pthread_join给拿到-----------因为pthread_join本来就是拿的return的值现在由pthread_exit完成这个“return”的任务。 pthread_cancel pthread_cancel :主要用于主线程去取消其他进程。类似于父进程去取消子进程的过程 但是不建议直接使用因为不清楚被取消的线程的工作状态 一个被pthread_cancle取消的线程的join值是-1ret 返回的值 这个接口要慎用如果在新线程未完成任务就被主线程结束主线程可能也会结束。 并且主线程在cancle的时候可能不清楚新线程的状态有可能出错。 一个被pthread_cancle取消的线程的join值是-1ret 2. CPP的封装 除了C语言对系统的相关线程操作进行了封装外其他编程语言也会做同样的事情这样可以确保语言具有可移植性同样C中也有对应的线程库thread具体接口见C线程库部分但是需要注意的是如果是在Linux下使用C线程库编写代码编译该代码时依旧需要携带-lpthread因为C线程库本质也是封装了libpthread.so 错误 lambda表达式构造thread