赞
踩
线程:是进程内的一个执行分支,线程的执行粒度,要比进程细。
task_struct
。在之前的认知中,我们都认为一个进程就是一个PCB + 程序的代码和数据。 但是现在我们要重新认识进程了。当进程内部只有一个执行流的时候, 进程 = PCB + 程序的代码和数据。 当进程内部有多个执行流的时候 ,那么 进程 = 多个PCB + 程序的代码和数据。
在CPU的视角中,CPU其实根本不关心当前调用的是进程还是线程,因为它只认PCB,也就是task_struct
。所以在linux系统下, PCB <= 其他OS内的PCB。因为当Linux下的进程包含多个执行流的时候,那么多个PCB其实共享了大部分资源,那么此时的PCB就会小于其他OS内的PCB。因为其他的OS,进程和线程都有属于各自的数据结构。
得出结论:
task_struct
) + 程序的代码和数据多进程之间的数据共享比多线程编程复杂,因为线程之间共享地址空间,因此通信更加方便,全局数据以及函数传参都可以实现,而进程间则需要系统调用来完成
多线程的创建,切换,销毁速度快于多进程,因为线程之间共享了进程中的大部分资源,因此共享的数据不需要重新创建或销毁,因此消耗上低于进程,反之也就是速度快于进程
大量的计算使用多进程和多线程都可以实现并行/并发处理,而线程的资源消耗小于多进程,而稳定向较多进程有所不如。
多线程没有内存隔离,单个线程崩溃会导致整个应用程序的退出,其实不仅仅是内存隔离的问题,还有就是异常针对的是整个进程,因此单个线程的崩溃会导致异常针对进程触发,最终退出整个进程。
一个程序至少有一个进程,一个进程至少有一个线程,这是错的,程序是静态的,不涉及进程,进程是程序运行时的实体,是一次程序的运行。
操作系统的最小调度单位是线程
进程是资源的分配单位,所以线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配。
任何一个线程都可以创建或撤销另一个线程
10 + 10 +12
的方式进行分割,前10个比特位是对页目录里的项表进行定位找到二级页表中的页表表项,中间的10个比特位是对二级页表的表项进行定位找到对应的物理地址,然后:后12比特位是对对应的起始地址 + 偏移量就找到了对应的物理地址。#include<iostream>
using namespace std;
class A
{};
int main()
{
cout << sizeof(A) << endl;
return 0;
}
在CPU内部是有一个cache
的缓存里面是存放的热数据
cache
缓存工作的基本原理是通过将部分主存中的数据复制到更快速但容量较小的缓存中,以便 CPU 在需要时能够更快地获取数据。当 CPU 需要访问数据时,首先检查缓存中是否存在该数据。如果存在(命中),则可以直接从缓存读取,避免了从主存中读取的延迟。如果不存在(未命中),则需要从主存中加载到缓存,并且通常会替换掉缓存中的某些旧数据。
而线程内的切换不需要重新加载cache数据,所以更加轻量化~~
但是地址空间和页表切换并没有太大的消耗。线程切换成本更低的本质原因是因为CPU内部有L1~L3 cache
。
性能损失
健壮性降低
缺乏访问控制
编程难度提高
进程是资源分配的基本单位
线程是调度的基本单位
线程共享进程数据,但也拥有自己的一部分数据:
同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程 中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
进程和线程的关系如下图:
内核中没有明确的线程的概念,有轻量级进程的概念
这也就意味着Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程接口。以库的方式提供给了用户进行使用,那就是pthread
线程库(在应用层),为用户提供直接创建线程的接口,也叫原生线程库。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
nullptr
EAGAIN
表示系统资源不足以创建新线程,EINVAL
表示提供的属性值无效,EPERM
表示调用进程没有足够的权限等。测试代码:
makefile:
-lpthread
选项,否则无法编译通过,因为原生线程库并不属于C/C++库,这是一个第三方库mythread:mythread.cc
g++ -o $@ $^ -std=c++11 -lp thread
.PHONY:clean
clean:
rm -rf mythread
mythread.cc:
#include <iostream> #include <string> #include <pthread.h> #include <unistd.h> using namespace std; void *ThreadRun(void *args) { const char *name = (const char *)args; while (true) { printf("%s, pid: %d\n", name, getpid()); sleep(1); } return (void *)name; // 返回线程名称作为退出值 } int main() { pthread_t tids[5]; for (size_t i = 0; i < 5; i++) { std::string name = "Thread "; name += std::to_string(i); pthread_create(&tids[i], nullptr, ThreadRun, (void *)name.c_str()); printf("Thread main, pid: %d\n", getpid()); sleep(1); } sleep(5); std::cout << "Main thread exiting.\n"; return 0; }
然后运行后我们发现。5个线程 + 一个主线程,它们打印出来的进程pid都是一样的
while :; do ps -axj | head -1 && ps -axj | grep -i mythread | grep -v grep; sleep 1; done;
ps -aL
即可查看当前进程下的线程。while :; do ps -aL | head -1 && ps -aL | grep -i mythread | grep -v grep; sleep 1; done;
我们可以看到这个进程中有6个线程,一个主线程。剩下的5个创建的线程。 我们可以发现它们的PID都是一样的。但是LWP(Light Weight Process)是不一样的! 所以,CPU调度看的是LWP,因为线程是CPU调度的基本单元。如果是根据PID进行调度,那么这么多线程的PID都一样,就会产生歧义。所以CPU调度实际是根据LWP字段调度的。
LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化
轻量级进程ID与进程ID之间的区别:
此外:我们还可以查看每个线程的CPU占用率
top
查看的是进程视角top -H
查看的是线程视角代码验证:
#include <iostream> #include <string> #include <pthread.h> #include <unistd.h> using namespace std; int g_val = 100; void *ThreadRun(void *args) { const char *name = (const char *)args; while (true) { printf("%s, pid: %d, g_val: %d, &g_val: 0x%p\n", name, getpid(), g_val, &g_val); g_val++; sleep(1); } return (void *)name; // 返回线程名称作为退出值 } int main() { pthread_t tids[5]; pthread_t tid; pthread_create(&tid, nullptr, ThreadRun, (void *)"Thread new"); while (true) { printf("Thread main, pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val); sleep(1); } sleep(5); std::cout << "Main thread exiting.\n"; return 0; }
如果其中一个线程出错了也会影响其他线程
比如在代码中出现了除0错误,会直接导致进程退出
线程共享进程数据,但也拥有自己的一部分数据,比如:
int pthread_join(pthread_t thread, void **retval);
PTHREAD_ CANCELED
。void *ThreadRun(void *args) { const char *name = (const char *)args; int cnt = 5; while (cnt) { printf("%s, cnt: %d\n", name, cnt); sleep(1); cnt--; } return (void *)name; // 返回线程名称作为退出值 } int main() { pthread_t tid; pthread_create(&tid, nullptr, ThreadRun, (void *)"Thread new"); void *retval; pthread_join(tid, &retval); printf("Joined with \"%s\"\n", (char *)retval); std::cout << "Main thread exiting.\n"; return 0; }
在学习进程的时候需要考虑进程出异常,而线程为什么不需要考虑异常?
exit
退出void *ThreadRun(void *args)
{
const char *name = (const char *)args;
int cnt = 5;
while (cnt)
{
printf("%s, cnt: %d\n", name, cnt);
sleep(1);
cnt--;
}
exit(11); // 直接退出
// return (void *)name; // 返回线程名称作为退出值
}
void pthread_exit(void *retval);
void *ThreadRun(void *args)
{
const char *name = (const char *)args;
int cnt = 5;
while (cnt)
{
printf("%s, cnt: %d\n", name, cnt);
sleep(1);
cnt--;
}
pthread_exit((char *)name);
// exit(11); // 直接退出
// return (void *)name; // 返回线程名称作为退出值
}
int pthread_cancel(pthread_t thread);
void *ThreadRun(void *args) { const char *name = (const char *)args; int cnt = 5; while (cnt) { printf("%s, cnt: %d\n", name, cnt); sleep(1); cnt--; } } int main() { pthread_t tid; pthread_create(&tid, nullptr, ThreadRun, (void *)"Thread new"); sleep(1); // 保证新线程已经启动 pthread_cancel(tid); // 取消线程 void *retval; pthread_join(tid, &retval); printf("Joined with \"%d\"\n", (int*)retval); std::cout << "Main thread exiting.\n"; return 0; }
-1
,起始是一个宏:PTHREAD_CANCELED
#define PTHREAD_CANCELED ((void *) -1)
void *PthreadRun(void *args) { int cnt = 0; while (true) { cout << "new Thread, cnt = " << cnt << endl; cnt++; sleep(1); } return nullptr; } int main() { pthread_t tid; pthread_create(&tid, nullptr, PthreadRun, nullptr); sleep(5); pthread_exit(nullptr); return 0; }
pthread_exit()
后,Z
代表僵尸进程,l
代表是一个多线程状态,+
代表前台进程class Request { public: Request(int start, int end, const string &threadname) : start_(start), end_(end), threadname_(threadname) { } public: int start_; int end_; string threadname_; }; class Response { public: Response(int result, int exitcode) : result_(result), exitcode_(exitcode) { } public: int result_; // 计算结果 int exitcode_; // 计算结果是否可靠 }; void *sumCount(void *args) // 线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!! { Request *rq = static_cast<Request *>(args); // Request *rq = (Request*)args Response *rsp = new Response(0, 0); for (int i = rq->start_; i <= rq->end_; i++) { cout << rq->threadname_ << " is runing, caling..., " << i << endl; rsp->result_ += i; usleep(100000); } delete rq; return rsp; } int main() { pthread_t tid; Request *rq = new Request(1, 100, "thread 1"); // 计算1到100的和 pthread_create(&tid, nullptr, sumCount, rq); // 创建线程 void *ret; pthread_join(tid, &ret); // 等待线程 Response *rsp = static_cast<Response *>(ret); // 相当于类型转换 cout << "rsp->result: " << rsp->result_ << ", exitcode: " << rsp->exitcode_ << endl; // 查看结果 delete rsp; return 0; }
C++11
语言本身也已经支持多线程了 vs 原生线程库
#include <thread> void threadrun() { while (true) { cout << "I am a new thead for C++" << endl; sleep(1); } } int main() { thread t1(threadrun); t1.join(); return 0; }
在编译的时候如果不加-lpthread
是编译不通过的。
其实C++11的多线程本质,就是对原生线程库接口的封装
pthread_ create
函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。NPTL
线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。线程库NPTL
提供了pthread_ self
函数,可以获得线程自身的ID。pthread_t pthread_self(void);
pthread_t
到底是什么类型呢?
pthread_t
类型的线程ID,本质就是一个进程地址空间上的一个地址。// 转换成十六进制 string toHex(pthread_t tid) { char hex[64]; snprintf(hex, sizeof(hex), "%p", tid); return hex; } void *threadRun(void *args) { while (true) { cout << "Thread id: " << toHex(pthread_self()) << endl; sleep(1); } } int main() { pthread_t tid; pthread_create(&tid, nullptr, threadRun, (void *)"Thread 1"); cout << "main thread create thead done, new thread id : " << toHex(tid) << endl; pthread_join(tid, nullptr); return 0; }
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
clone
接口被线程库封装了!pthread_create
之类的接口,然后通过clone
(回到函数,独立栈)进行操作OS
#define NUM 10 struct threadData { string threadname; }; string toHex(pthread_t tid) { char buffer[128]; snprintf(buffer, sizeof(buffer), "%#lx", tid); return buffer; } void InitThreadData(threadData *td, int number) { td->threadname = "thread-" + to_string(number); // thread-0 } // 所有的线程,执行的都是这个函数 void *threadRoutine(void *args) { // 每个线程的test_i都是独立的,互不干扰的 int test_i = 0; threadData *td = static_cast<threadData *>(args); int i = 0; while (i < 10) { printf("pid: %d, tid:%s, threadname: %s, test_i: %d, &test_i: %p\n", getpid(), toHex(pthread_self()).c_str(), td->threadname.c_str(), test_i, &test_i); test_i++; sleep(1); i++; } delete td; return nullptr; } int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 sleep(1); } sleep(1); // 确保复制成功 return 0; }
#define NUM 10 int *p = NULL; struct threadData { string threadname; }; string toHex(pthread_t tid) { char buffer[128]; snprintf(buffer, sizeof(buffer), "%#lx", tid); return buffer; } void InitThreadData(threadData *td, int number) { td->threadname = "thread-" + to_string(number); // thread-0 } // 所有的线程,执行的都是这个函数 void *threadRoutine(void *args) { // 每个线程的test_i都是独立的,互不干扰的 int test_i = 0; threadData *td = static_cast<threadData *>(args); if(td->threadname == "thread-2") p = &test_i; int i = 0; while (i < 10) { printf("pid: %d, tid:%s, threadname: %s, test_i: %d, &test_i: %p\n", getpid(), toHex(pthread_self()).c_str(), td->threadname.c_str(), test_i, &test_i); test_i++; sleep(1); i++; } delete td; return nullptr; } int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 // sleep(1); } sleep(1); // 确保复制成功 cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl; return 0; }
#define NUM 3 int g_val = 100; struct threadData { string threadname; }; string toHex(pthread_t tid) { char buffer[128]; snprintf(buffer, sizeof(buffer), "%#lx", tid); return buffer; } void InitThreadData(threadData *td, int number) { td->threadname = "thread-" + to_string(number); // thread-0 } // 所有的线程,执行的都是这个函数 void *threadRoutine(void *args) { // 每个线程的test_i都是独立的,互不干扰的 threadData *td = static_cast<threadData *>(args); int i = 0; while (i < 10) { printf("pid: %d, tid:%s, threadname: %s, g_val: %d, &g_val: %p\n", getpid(), toHex(pthread_self()).c_str(), td->threadname.c_str(), g_val, &g_val); g_val++; sleep(1); i++; } delete td; return nullptr; } int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 sleep(1); } sleep(1); // 确保复制成功 return 0; }
__thread
,让他变成线程的局部存储,这个修饰符是一个编译选项,只能定义内置类型,不能用来修饰自定义类型__thread int g_val = 100;
#define NUM 3 __thread unsigned int number = 0; __thread int pid = 0; struct threadData { string threadname; }; string toHex(pthread_t tid) { char buffer[128]; snprintf(buffer, sizeof(buffer), "%#lx", tid); return buffer; } void InitThreadData(threadData *td, int number) { td->threadname = "thread-" + to_string(number); // thread-0 } // 所有的线程,执行的都是这个函数 void *threadRoutine(void *args) { threadData *td = static_cast<threadData *>(args); // if (td->threadname == "thread-2") // p = &test_i; string tid = toHex(pthread_self()); int pid = getpid(); int i = 0; while (i < 10) { cout << "tid: " << tid << ", pid: " << pid << endl; sleep(1); i++; } delete td; return nullptr; } int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 sleep(1); } sleep(1); // 确保复制成功 return 0; }
joinable
的,线程退出后,需要对其进行pthread_join
操作,否则无法释放资源,从而造成系统泄漏。join
是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。int pthread_detach(pthread_t thread);
pthread_detach(pthread_self());
joinable
和分离是冲突的,一个线程不能既是joinable
又是分离的。int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 sleep(1); } sleep(1); // 确保复制成功 // 分离线程 for (auto i : tids) { pthread_detach(i); } return 0; }
int main() { // 创建多线程 vector<pthread_t> tids; for (int i = 0; i < NUM; i++) { pthread_t tid; // 线程名字 threadData *td = new threadData; // 这里使用new,在堆上开空间,每个线程独享 // 初始化线程id InitThreadData(td, i); // 创建 pthread_create(&tid, nullptr, threadRoutine, td); tids.push_back(tid); // 将每个线程使用vector管理起来 sleep(1); } sleep(1); // 确保复制成功 // 分离线程 for (auto i : tids) { pthread_detach(i); } // pthread_detach后再pthread_join会出错 for (int i = 0; i < tids.size(); i++) { int n = pthread_join(tids[i], nullptr); printf("n = %d, who = 0x%lx, why: %s\n", n, tids[i], strerror(n)); } return 0; }
// 所有的线程,执行的都是这个函数 void *threadRoutine(void *args) { // 自己把自己分离 pthread_detach(pthread_self()); threadData *td = static_cast<threadData *>(args); string tid = toHex(pthread_self()); int pid = getpid(); int i = 0; while (i < 10) { cout << "tid: " << tid << ", pid: " << pid << endl; sleep(1); i++; } delete td; return nullptr; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。