线程的概念
线程(Thread)是操作体系中进行程序实验的最小单位,也是程序调理和分派的根本单位。它通常被包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行实验不同的使命。
像之前所有实验的程序,都是用main作为主函数,单线程实验的;一切的语句都是在main函数中从上至下依次进行的;如果一条语句阻塞了,那么整个进程都将阻塞;
线程的特点
并发实验:线程是进程内的一条实验路径或控制单位,因此多个线程可以在同一进程中并发实验,共享进程的资源(如内存空间、文件句柄等)。
独立调理:线程作为体系调理的根本单位,体系能独立地分配CPU给线程,从而确保每个线程都能独立运行。
轻量级:线程的创建、销毁和切换比进程更快速,由于线程间的资源共享减少了资源分配和回收的开销。
同步与互斥:由于多个线程大概同时访问共享资源,因此需要利用同步机制(如互斥锁、条件变量等)来确保数据的一致性和正确性。
多线程编程:多线程编程模子允许开发者编写能够并发实验多个使命的应用程序,以进步程序的性能和响应本领。
线程的主要特点就是能够在进程中并发实验,对于一项使命来说,如果是流程分布的,那么单人打工和多人打工的服从可想而知,多线程的服从能够大大进步;而且相对于进程来说,它的开销更小,也就是比进程的量级小,这样我们可以有效利用资源,进步一切有用服从。
线程与进程的区别
进程是操作体系资源分配和调理的根本单位;
线程是进程的一部分,是CPU调理和分配的根本单位;
每个进程都拥有自己独立的地址空间和体系资源,进程之间的资源是独立的;
线程不拥有体系资源,它们共享其所属进程的资源;
每个独立的进程都有一个程序运行的入口、顺序实验序列和程序入口;
线程不能独立实验,必须依存在应用程序中,由应用程序提供多个线程实验控制;
线程怎样访问到内存(页表的进一步理解)
之前我们一直讲述,进程拥有自己的进程地址空间,上面的地址都是假造地址,需要通过页表的映射找到对应的物理内存;
那对于线程是怎样找到对应内存的?
线程自己并不通过页表映射物理内存找到对应物理地址。线程是进程的一部分,它们共享进程的地址空间,包罗进程的页表。当线程在访问内存时,实际上是进程在进程内存访问。因此页表的映射过程是在进程层面进行的。
下面简述线程是怎样找到对应内存的:
- 假造地址的生成:当线程需要访问内存时,它会在进程中产生一个假造地址,这个假造地址就是在进程地址空间的。
- 页表查找:CPU利用假造地址的页号部分作为索引来查找进程的页表。页表是一个包含多个表项的数据结构,每个页表都对应一个假造页面,并纪录该页面在物理内存中的位置或其他相干信息。(雷同于我们找到一本书,翻开目录查找相应页数中的内容);
- 页表项分析:CPU从页表中获取与假造地址对应的页表项。
- 构建物理地址:CPU将页表项中的物理页帧号与假造地址的页内偏移量相结合,生成一个完整的物理地址。该物理地址指向物理内存中实际存储数据的位置。
- 内存访问:CPU利用这个物理地址来访问内存中的数据。如果页面已经存在于物理内存中(即该页面已经被加载到内存中),则CPU可以直接从物理内存中读取或写入数据(共享内存)。如果页面不存在于物理内存中(即发生了页错误),则操作体系将触发页面置换算法来选择一个页面进行置换,并将所需的页面从磁盘或其他存储介质中加载到物理内存中。
线程的控制
简朴利用
- void* newthreadrun()
- {
- while(true)
- {
- cout<<"this is new thread:"<<getpid()<<endl;
- sleep(1);
- }
- }
- int main()
- {
- //1.id
- //2对于新线程和主线程哪个先运行,由调度器决定
- pthread_t tid;
- void* ret=nullptr;
- pthread_create(&tid,nullptr,newthreadrun,nullptr);
-
- while(true)
- {
- cout<<"this a main thread:"<<getpid()<<endl;
- sleep(1);
- }
- return 0;
- }
复制代码 有关函数:
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
- void *(*start_routine) (void *), void *arg);
复制代码 参数说明:
pthread_t *thread:这是一个指向 pthread_t 类型的指针,用于获取新创建线程的标识符。这个标识符可以在其他线程函数中被引用,以便进行线程间的同步或等待其他线程结束。
const pthread_attr_t *attr:这是一个指向 pthread_attr_t 类型的指针,用于设置线程属性。大多数情况下,这个参数可以设置为 nullptr,利用默认的线程属性。
void *(*start_routine) (void *):这是一个指向线程函数的指针,当新线程被创建时,这个函数将被调用。这个函数应该返回一个 void * 类型的指针,而且继承一个 void * 类型的参数。
void *arg:这是传递给线程函数的参数。它可以是任何数据类型,但通常会被强制转换为 void * 类型。在线程函数内部,你可以将其转换回原来的类型。
函数返回值:
如果成功,pthread_create 将返回 0。
如果失败,它将返回一个错误码,你可以利用 strerror 或 perror 函数来获取关于这个错误码的详细信息。
效果:
查看进程信息:
查看线程信息:
指令ps-aL:用于显示当前体系中进程和线程的信息;
PID:进程ID
LWP:light weight process 轻量级进程呈(给客户就是对应的线程)
id
- string ToHex(pthread_t tid)
- {
- char id[64];
- snprintf(id,sizeof(id),"0x%lx",tid);
- return id;
- }
- void* newthreadrun()
- {
- while(true)
- {
- cout<<"this is new thread:"<<getpid()<<endl;
- cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- }
- int main()
- {
- //1.id
- //2.对于新线程和主线程哪个先运行,由调度器决定
- pthread_t tid;
- pthread_create(&tid,nullptr,newthreadrun,nullptr);
- while(true)
- {
- cout<<"this a main thread:"<<getpid()<<endl;
- cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- return 0;
- }
复制代码
传参
- string ToHex(pthread_t tid)
- {
- char id[64];
- snprintf(id,sizeof(id),"0x%lx",tid);
- return id;
- }
- void* newthreadrun(void* args)
- {
- string threadname=(char*)args;
-
- while(true)
- {
- cout<<threadname<<endl;
- cout<<"this is new thread:"<<getpid()<<endl;
- cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- }
- int main()
- {
- //1.id
- //2对于新线程和主线程哪个先运行,由调度器决定
- pthread_t tid;
- void* ret=nullptr;
- pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
- while(true)
- {
-
- cout<<"this a main thread:"<<getpid()<<endl;
- cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- return 0;
- }
复制代码
等待退出
pthread_join 可以确保线程的资源得到正确的整理。当一个线程终止时,它的资源(如栈内存)不会主动释放,直到另一个线程调用 pthread_join 或线程是分离的(通过 pthread_detach 或设置属性)。如果线程没有被连接或分离,那么它的资源将不会被释放,这大概会导致内存泄漏。
- string ToHex(pthread_t tid)
- {
- char id[64];
- snprintf(id,sizeof(id),"0x%lx",tid);
- return id;
- }
- void* newthreadrun(void* args)
- {
- string threadname=(char*)args;
-
- while(true)
- {
- cout<<threadname<<endl;
- cout<<"this is new thread:"<<getpid()<<endl;
- cout<<"newthread thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- }
- int main()
- {
- //1.id
- //2对于新线程和主线程哪个先运行,由调度器决定
- pthread_t tid;
- void* ret=nullptr;
- pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
- while(true)
- {
-
- cout<<"this a main thread:"<<getpid()<<endl;
- cout<<"main thread id: "<<ToHex(pthread_self())<<endl;
- sleep(1);
- }
- int n=pthread_join(tid,&ret);
- cout << n << endl;
- sleep(1);
- return 0;
- }
复制代码 有关函数:
- int pthread_join(pthread_t thread, void **retval);
复制代码 pthread_t thread:这是你想要等待的线程的标识符。这个标识符是通过 pthread_create函数返回的。
void **retval:这是一个指向指针的指针,用于获取被等待线程的返回值。如果 retval 不是 nullptr,那么 pthread_join 将把被等待线程的返回值存储在 retval 所指向的位置。如果被等待线程没有返回值(即线程函数返回 NULL 或 pthread_exit 被调用时没有指定返回值),则 *retval 的内容将是不确定的。如果你不关心线程的返回值,可以将 retval 设置为 NULL。
函数返回值:
如果成功,pthread_join 将返回 0。
如果失败,它将返回一个错误码。
效果:
修改:
如果主线程先退出,新线程会怎么样?
资源共享
资源共享是指多个线程可以访问和利用同一进程中的某些资源。
- int g_val = 100;
- string ToHex(pthread_t tid)
- {
- char id[64];
- snprintf(id,sizeof(id),"0x%lx",tid);
- return id;
- }
- void* newthreadrun(void* args)
- {
- string threadname=(char*)args;
- int cnt=5;
- while(cnt--)
- {
- printf("new thread, g_val: %d, &g_val: %p\n", g_val, &g_val);
- g_val++;
- sleep(1);
- }
- pthread_exit((void*)123);
- }
- int main()
- {
- pthread_t tid;
- void* ret=nullptr;
- pthread_create(&tid,nullptr,newthreadrun,(void*)"thread-1");//传参
- while(cnt--)
- {
- printf("main thread, g_val: %d, &g_val: %p\n", g_val, &g_val);
-
- sleep(1);
- }
- int n=pthread_join(tid,&ret);
- cout<<"main thread quit: "<<n<<" main thread get a ret: "<<(long long)ret<<endl;
- sleep(1);
- return 0;
- }
复制代码 效果:
新线程堕落
线程的终止
正确操作:
线程的优缺点
线程的优缺点其实上面的简述都有提及,下面就来总结下:
优点:
- 资源共享:线程共享它们所属的进程的资源,包罗内存地址空间、全局变量、文件句柄等。这使得线程间的通讯和数据共享变得轻易。
- 减少开销:线程的创建和销毁比进程的创建和销毁所需的资源要少得多。因此,在需要频仍创建和销毁实验单位的情况下,利用线程更为高效。
- 独立性:线程是独立的实验路径,它们可以并发实验,互不干扰。这有助于进步程序的并行性和响应本领。
- 多处置惩罚器支持:多线程可以充分利用多处置惩罚器的上风,实现真正的并行处置惩罚。通过将一个使命分解为多个线程,可以同时在多个处置惩罚器上实验这些线程,从而加快使命的完成速度。
- 简化编程:在某些情况下,利用线程可以简化编程。例如,可以利用线程来实现异步操作或并行计算等复杂使命。
缺点:
- 资源竞争:由于线程共享进程的资源,因此大概会出现资源竞争的问题。当多个线程同时访问同一资源时,大概会导致数据不一致或其他问题。为了解决这个问题,需要利用同步机制(如锁、信号量等)来确保线程之间的协调。
- 编程复杂性:线程编程通常比进程编程更为复杂。程序员需要处置惩罚线程间的同步和通讯问题,以及克制死锁、竞态条件等潜在问题。
- 体系稳定性:多线程程序大概更轻易出现错误和崩溃。当多个线程并发实验时,它们大概会相互干扰或竞争资源,从而导致程序的不稳定或崩溃。别的,如果线程管理不当(如创建过多的线程),也大概导致体系资源的耗尽和性能降落。
- 安全性问题:多线程程序中大概存在安全问题。例如,如果一个线程可以访问另一个线程的私有数据或实验敏感操作,那么大概会导致数据泄露或体系被攻击。为了解决这个问题,需要利用访问控制和其他安全机制来保护线程之间的数据和操作。
线程的资源
线程的私有资源
- 线程栈(Thread Stack):每个线程都有自己独立的栈空间,用于存储局部变量、函数调用和返回地址等信息。线程栈在创建线程时分配,并在线程结束时释放。
- 线程ID(Thread ID):每个线程都有一个唯一的标识符,称为线程ID,用于在操作体系中唯一标识该线程。
- 寄存器上下文(Register Context):线程在实验过程中会利用到各种寄存器,如程序计数器(PC)、栈指针(SP)等。这些寄存器的状态对于每个线程来说都是私有的,并在线程切换时被保存和恢复。
- 线程本地存储(Thread-Local Storage, TLS):线程本地存储是一种特殊的存储区域,允许每个线程存储其私有的全局变量。这些变量在逻辑上是全局的,但在物理上每个线程都有自己独立的副本。
- 错误处置惩罚:每个线程都有自己的错误处置惩罚上下文,包罗异常处置惩罚机制、错误码等。这些机制允许线程独立地处置惩罚自己遇到的错误和异常情况。
线程的共享资源
- 代码段(Code Segment):进程中的代码段是共享的,包罗程序中的函数、变量界说等。多个线程可以并发地实验相同的代码段。
- 数据段(Data Segment):进程中的全局变量和静态变量位于数据段,这些数据对于进程中的所有线程都是可见的。然而,线程对这些数据的访问需要同步机制来克制竞态条件和数据不一致的问题。
- 堆(Heap):进程中的堆空间也是共享的,用于动态分配内存。多个线程可以同时从堆中分配和释放内存,但同样需要同步机制来确保内存访问的安全性和一致性。
- 文件描述符(File Descriptors):进程打开的文件和其他I/O资源(如套接字)由文件描述符表示。这些文件描述符对于进程中的所有线程都是共享的,允许线程之间共享文件和I/O操作。
- 信号(Signals):操作体系发送给进程的信号也是共享的。当进程收到一个信号时,操作体系会选择一个线程来处置惩罚该信号。通常,进程的主线程或特定的信号处置惩罚线程会负责处置惩罚这些信号。
- 进程环境:进程的环境变量、打开的文件句柄、信号掩码等也是共享的,但它们通常被视为进程级别的资源,而不是直接由线程管理的资源。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |