即墨网站制作,wordpress如何设置中英文切换,注册域名卖钱很暴利吗,济源网站建设电话目录 前言
一、线程创建
1.创建线程
2.线程传递结构体
3.创建多线程
4.收到信号的线程
二、线程终止
三、线程等待
四、线程分离
五、取消线程
六、线程库管理的原理
七、站在语言角度理解pthread库
八、线程的局部存储 前言
前面我们学习了线程概念和线程创建今天我们学习线程控制如何操控一个线程完成任务同时能取消线程、等待线程分离线程。
一、线程创建
1.创建线程 功能创建一个新的线程 参数 thread:返回线程IDattr:设置线程的属性attr为NULL表示使用默认属性start_routine:是个函数地址线程启动后要执行的函数arg:传给线程启动函数的参数返回值成功返回0失败返回错误码 #includeiostream
#includeunistd.h
#includepthread.h
using namespace std;void* TreadToutine(void *arg)
{const char* threadname (const char*) arg;while(1){cout我是一个新线程threadnameendl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(tid,NULL,TreadToutine, (void*)thread 1);//主线程while(1){cout我是主线程endl;sleep(1);}return 0;
}
linux没有真正的线程概念他的线程是复用的进程代码只是做了一些区分。线程客观的可以叫做轻量级进程。因此Linux只会提供轻量级进程创建的函数调用不会直接提供线程创建的接口。因此我们使用pthread原生线程库编译时需要手动链接库文件-lpthread。
这样编译后就可以运行了。 从上面代码可以看出给线程传递的信息可以是char*由于pthread_create函数的最后一个参数为void*同时线程去运行的函数参数也是void*因此我们任意类型都可以传递过去进行一下强转即可。 2.线程传递结构体
比如现在我想传递很多内容过去叫线程帮我们处理 如下我们传递了结构体 线程成功收到结构体并做出了处理。 3.创建多线程 创建多线程也很简单只需要循环创建即可。 4.收到信号的线程
如果进程创建的线程有一个发生了异常收到了信号会导致整个进程都被终止因为线程是进程创建出来的发送信号是发给了进程进程如果退出那么该进程所有的资源也都得被回收。而线程本身就是进程资源的一部分。 二、线程终止
我们知道线程去执行的函数返回类型为void*当线程执行结束return时线程就自动终止了。
如果我们返回时调用exit()函数 那么整个进程都会被终止。
同时pthread.h库还给我们提供了 pthread_exit() 接口我们使用该接口也可以终止线程。 pthread_exit() 作用终止一个运行的线程 参数retval返回void*的全局变量 注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的不能在线程函数的栈上分配因为当其它线程得到这个返回指针时线程函数已经退出了。 代码如下两种方法都可以退出 运行结果如下线程被退出不再打印消息。 三、线程等待
线程退出默认要被等待如果不等待就会发生类似于僵尸进程的问题。因此我们需要用pthread_join()函数进行等待 pthread_join() 功能等待线程结束 参数1:thread:线程ID参数2value_ptr:它指向一个指针后者指向线程的返回值void**指向的线程返回值void*返回值成功返回0失败返回错误码 代码如下让子线程程循环5次后退出并传参常量字符串主线程去join等待并将等待的结果输出。
#include iostream
#include unistd.h
#include pthread.h
#include cstdio
using namespace std;class Add
{
public:Add(string name, int a, int b): _name(name), _a(a), _b(b){}public:string _name;int _a;int _b;
};void *TreadToutine(void *arg)
{Add *a1 (Add *)arg;int cnt 5;while (cnt--){cout 我是一个新线程: a1-_name ,计算结果为 a1-_a a1-_b endl;sleep(1);}//return nullptr;pthread_exit((void*)pthread-1 退出); //常量区
}int main()
{pthread_t tid;Add *td new Add(thread-1, 10, 20);pthread_create(tid, NULL, TreadToutine, td);// 主线程cout 我是主线程,子线程的tid: tid endl;void* msg nullptr;pthread_join(tid,msg);cout等待成功,子线程退出信息: (char*)msg endl;sleep(1);return 0;
}
等待成功同时输出了消息。注意等待是阻塞式等待子线程退出后才会执行后续代码。 四、线程分离
我们知道线程是需要被等待的不然会发生类似于僵尸进程的现象那么如果我想让线程一直去运行比如说一直帮我播放音乐那么主线程就会一直等待不可能执行后面的代码。
在这种情况下我们可以让线程分离也就是主线程不再关心创建的子线程的死活他要运行就运行不运行了操作系统会回收。不过一般建议主线程最后再退出。
可以使用pthread_detach()函数进行线程的分离。 pthread_detach() 作用分离线程 分离线程很简单直接调用pthread_detach()就可以我们不过多展示下面代码是先分离线程再等待线程看看会发生什么。 发现等待线程的返回值为22不是0证明等待失败22的意思是该线程不需要等待。 这是我们是在主线程进行分离的子线程也可以被分离 由于子线程默认看不到自己的tid因此可以调用pthread_self()函数获取自己的tid。 pthread_self 作用让线程获取自己的tid 如下是子线程选择分离。 小总结 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。 五、取消线程
主线程可以取消线程也就是让子线程退出可以调用pthread_cancel()函数进行终止线程。 pthread_cancel() 功能向线程发送取消请求 参数1thread线程ID返回值成功返回0失败返回错误码 代码如下先取消进程再等待线程同时查看线程退出码
#include iostream
#include unistd.h
#include pthread.h
#include cstdio
using namespace std;void *TreadToutine(void *arg)
{while(1){cout 我是一个新线程 endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(tid, NULL, TreadToutine, (void *)pthread-1);sleep(3);// 取消线程int n pthread_cancel(tid);cout 线程取消成功,n: n endl;// 等待线程void *ret nullptr;n pthread_join(tid, ret);cout 等待线程返回值n: n ,线程返回值: (int64_t)ret endl;return 0;
}
运行看到线程返回值为0取消成功等待返回值为0等待成功。我们看到线程没有阻塞在等待函数这里而是直接往后运行同时进程返回为-1。 这是因为如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED 而如果线程先被脱离再取消结果怎么样呢 发现也是能被取消的但是线程等待是22等待失败。因为系统直接回收了。 小总结 1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。 2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。 3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。 4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。 六、线程库管理的原理
我们对线程的操作一直要使用tid那么tid里面的内容到底是什么呢
其实他是一个地址我们转成16进程来看一下。 确实是很像是地址但这跟LWPLight Weight Process也不一样啊。该如何理解呢 首先我们知道pthread. h不是操作系统的接口而是原生线程库。那么用户创建的线程操作系统无法管理则需要线程库来进行管理。他从系统中获取轻量级进程相关属性从用户中也获取一些属性这样就先描述起来了再通过数据结构将线程组织起来就将线程管理好了。我们也知道线程要有独立属性独立的主要有硬件上下文和栈空间其中硬件上下文跟操作系统有关而栈空间则是要从用户中来。栈不是只有一个吗为什么每一个线程都有自己的栈空间呢这其实是操作系统帮我们处理了的操作系统会在堆区创建空间来充当线程独立的栈。pthread库会获取到栈空间并将他管理维护好而默认地址空间中的栈由主线程使用。 那么线程库如何管理呢在哪管理呢 在进程地址空间中mmap共享区加载了动态库其中我们使用的pthread库就在该区域他会管理好每一份线程每一份线程都在其中有自己的属性集。struct pthread里存在很多线程属性线程局部存储还有线程栈这个栈指向的是堆空间的区域每当有新线程被创建都会在后面继续创建这种数据结构。就这样将多个线程统一的描述组织起来了可以进行管理了。因此我们调用pthread相关函数相当于对该空间进行访问、处理。 那么现在我们也可以理解 pthread_t tid 是什么了他不就是每一个线程在进程地址空间的起始地址嘛我们pthread_create 对tid进行写入因为需要创建对应的数据结构找到起始地址然后返回后续用户要继续对线程进行控制等待啊终止啊分离啊取消啊。都需要传入tid也就是能找到在进程地址空间的位置后才可以处理。
七、站在语言角度理解pthread库
我们之前学的pthread库是Linux提供的原生线程库在语言层面比如C/JAVA\PYTHON他们也会提供给我们线程库。
我们写了一份代码使用的是C提供的线程库 thread #includeiostream#includeunistd.h#includethreadusing namespace std;void myrun()
{while(1){cout我是一个新线程endl;sleep(1);}
}int main(){thread t(myrun);t.join();}
编译后运行发现说多线程操作被禁止了这是因为我们没有链接pthread库。 c提供的线程库封装了pthread.h。因此我们编译时仍然需要链接 pthread库。 到现在我们可以知道语言上也许线程库的使用不一定相同但是他们底层都是用的linux原生线程库。
在Linux下做了封装那么这段代码我们可以在Linux中运行。
如果thread头文件在Windows下封装了Windows线程的操作那么也可以在Windows下运行。这大大提高了文件的可移植性。
八、线程的局部存储
我们定义一个全局变量创建线程让新线程对全局变量做观察新线程和主线程全局变量是否发生变化。
#include iostream
#include unistd.h
#include pthread.h
using namespace std;int g_val 100;void *TreadToutine(void *arg)
{while (1){cout 我是一个新线程,g_val: g_val ,g_val: g_val endl;g_val;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(tid, NULL, TreadToutine, (void *)Thread 1);while (1){cout 我是一个主线程,g_val: g_val ,g_val: g_val endl;sleep(1);}pthread_join(tid,nullptr);
}我们可以看到全局变量值一样地址也一样我们现在知道全局变量是被所有进程共享的。 如果我们给全局变量前添加上__threadGCC/G编译器提供的一个扩展用于声明线程局部存储变量。 现在运行主线程和新线程g_val不一样地址也不一样。 因为我们添加的__thread 会在G编译时给每个线程的局部存储空间里将变量拷贝进程私有一份于是每个线程自己管理自己的那一份资源。不再与外部共享。 只是__thread只能修饰内置类型如string这种自定义类型无法处理。