一. Linux线程控制
1.1 验证线程的问题
验证线程是可以被创建出来的,以及在Linux中线程是用轻量级进程模拟实现的
cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
void *threadrun(void *arg)
{
std::string name((char *)arg);
while (1)
{
std::cout << "我是一个新线程,name:" << name << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadrun, (void *)"thread-1");
while (1)
{
std::cout << "我是主线程" << std::endl;
sleep(1);
}
return 0;
}

pthread_create创建了一个新线程,新线程执行threadrun函数,主线程继续向下执行
新线程执行的threadrun的起始地址就是新线程的入口地址,main函数的地址就是主线程的入口地址,两个线程执行各自的代码!
对于线程执行不同代码的代码块划分,根本不用我们划分,编译器已经为我们划分好了!

undefined reference to pthread_create没有被定义是因为pthread是第三方库,不是C/C++的标准库,因此在链接的时候需要指明链接的库名称,不需要指明查找路径是因为这个库在lib64目录下,系统会自动去到这个路径下查找




通过上面的 ps axj | head -1 && ps axj | grep TestThread指令查询到只有一个进程,可以验证创建的是两个线程,线程是进程的一个分支。使用kill -9 杀死进程,两个线程都被干掉,说明信号是发给进程的,进程一旦退出,其线程分支也会退出

使用ps -aL可以查看OS中运行的线程

可以看到两个线程的pid是一样的,但是LWP不一样,LWP:light weight process就是轻量级进程
pid和LWP相同的线程是主线程
那CPU在调度的时候是看PID还是LWP?
LWP,因为线程是CPU调度的基本单位,LWP是线程编号
那之前的进程为什么使用PID进行调度?
因为之前的进程是单进程,PID==LWP,所以用PID也是可以进行调度的。
这样就能说明Linux中是用轻量级进程模拟实现的线程。
线程的时间片划分问题:
当一个进程创建了多个线程后,这些线程共享进程的时间片,也就是进程的时间片被等分,这样就避免了有的恶意程序创建多个线程来占据时间片
线程异常一个线程出现异常,整个进程都会被干掉
.

消息混杂是因为两个线程访问的是同一个共享的显示器文件,如果想要避免这种情况需要加锁。
1.2 pthread线程库(POSIX线程库)
为什么要有这个库?这个库是什么东西

pthread库叫做原生线程库
cpp
// C++11
void threadrun()
{
while (1)
{
std::cout << "我是一个新线程" << std::endl;
sleep(1);
}
}
int main()
{
std::thread t(threadrun);
while(1)
{
std::cout << "我是主线程" <<std::endl;
sleep(1);
}
t.join();
return 0;
}

1.3 线程控制
1.3.1 线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
pthread_t *thread是线程的id,输出型参数const pthread_attr_t *attr线程的属性,默认是nullptrvoid *(*start_routine) (void *)线程要执行的函数的入口,返回值为void* 参数为void* 的函数void *arg传给线程的参数- 返回值:成功返回0,失败返回错误号
1.3.2 线程等待
int pthread_join(pthread_t thread, void **retval);
On success, pthread_join() returns 0; on error, it returns an error number.
pthread_t thread 线程ID
void **retval线程退出结果,因为线程调用函数的返回值是指针,要接受这个返回值就需要传指传参,就需要二级指针。
线程创建好后,主线程必须等待回收新线程,否则会造成类似僵尸进程,导致内存泄漏!
pthread_self()返回当前线程的线程号
cpp
void showid(pthread_t &tid)
{
printf("%ld", tid);
}
std::string Fomatid(const pthread_t &tid)
{
char ch[64];
snprintf(ch, sizeof(ch), "0x%lx", tid);
return ch;
}
void *rout(void *args)
{
std::string name = static_cast<const char *>(args);
int cnt = 3;
while (cnt--)
{
sleep(1);
std::cout << "我是一个新线程,name:" << name << "线程id:" << Fomatid(pthread_self()) << std::endl;
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, rout, (void *)"thread-1");
showid(tid);
int cnt = 3;
while (cnt--)
{
std::cout << "我是main线程,线程号:" << Fomatid(pthread_self()) << std::endl;
sleep(1);
}
return 0;
}

为什么看到的线程号和LWP不一样呢?因为在用户层面不能让用户看到LWP的值,用户层只知道线程而不能知道轻量级进程的概念。
主线程和新线程都能够调用同一个函数,因为他们共享地址空间,而且主线程和新线程同时修改全局变量,全局变量也不会发生写时拷贝
在主线程和新线程同时调用Formatid函数的时候,这个函数被重入了,叫做可重入函数,但是没有涉及全局的变量,这个函数是没有问题的

cpp
class Task
{
public:
Task(int a, int b) : _a(a), _b(b)
{
}
int Excute()
{
return _a + _b;
}
~Task() {}
private:
int _a;
int _b;
};
class Result
{
public:
Result(int ret) : _result(ret)
{
}
int GetRes() { return _result; }
~Result() {}
private:
int _result;
};
void *rout(void *args)
{
Task *t = static_cast<Task *>(args);
Result *res = new Result(t->Excute());
sleep(1);
return res;
}
int main()
{
pthread_t tid;
Task *t = new Task(1, 2);
pthread_create(&tid, nullptr, rout, t);
Result *ret = nullptr;
pthread_join(tid, (void **)&ret);
std::cout << "ret = " << ret->GetRes() << std::endl;
return 0;
}
对于pthread_create创建的新线程,执行新线程的函数,可以传任意类型的参数,只要做好类型转换就好。
- main 函数结束代表主线程结束,一般也代表进程结束
- 新线程对应的入口函数结束,代表当前线程结束
1.3.3 线程终止
-
线程的入口函数终止,线程也就终止了!!
能不能用
exit()函数终止线程呢?答案是不能的,因为exit()是用来终止进程的2.pthread_exit()
cpp
NAME
pthread_exit - terminate calling thread
SYNOPSIS
#include <pthread.h>
void pthread_exit(void *retval);
void *retval传void* 类型的指针来作为返回值

pthread_exit(res)和return res效果等效
- pthread_cancel

取消线程一定是在目标线程跑起来之后取消,线程被取消,返回值是-1,是PTHREAD_CANCELED
线程退出,pthread_join输出型参数拿到的返回值一定就是线程设定的返回值,如果线程异常,进程终止,pthread_join也拿不到结果
1.3.4 线程分离
在线程中有阻塞等待和非阻塞等待回收子进程的方法,0(阻塞)和WNOHANG(非阻塞),但是线程等待回收只有阻塞回收,那想要新线程结束后自动回收释放,不要主线程阻塞等待回收有什么方法吗?
将线程的状态由默认的joinable修改为分离状态(!joinable)。
线程分离:主线程将新线程在主线程中分离出去;新线程将自己在主线程中分离出去
cpp
NAME
pthread_detach - detach a thread
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);// 分离的线程号
RETURN VALUE
On success, pthread_detach() returns 0; on error, it returns an error number.
cpp
//主线程将新线程分离
int main()
{
pthread_t tid;
Task *t = new Task(1, 2);
pthread_create(&tid, nullptr, rout, t);
pthread_detach(tid);
return 0;
}
// 新线程将自己在主线程中分离
void *rout(void *args)
{
pthread_detach(pthread_self());
return res;
// pthread_exit(res);
}
分离的线程,依旧在进程的地址空间中,进程的所有资源,被分离的线程依旧可以访问可以操作。只是主进程不再等待新进程了。如果主线程join被分离的线程,join会失败报错!

1.3.5 创建多线程

这里我们创建了10个线程,为什么这10个线程的i值都是9呢?
因为每次传给新线程的是id的起始地址,而在for循环每次进行的时候,id数组不会重新创建而是会重写覆盖掉原来的数组内容,因此在新进程sleep的时候,原来的数据被覆盖掉了,即使将id写成全局的数组也没办法改变这一情况。
cpp
void *routine(void *args)
{
sleep(1);
std::string name = static_cast<const char *>(args);
delete (char*)args;
int cnt = 5;
while (cnt--)
{
std::cout << "我是新线程:name" << name << std::endl;
}
return nullptr;
}
int main()
{
std::vector<pthread_t> tids;
// 创建多线程
for (int i = 0; i < num; i++)
{
pthread_t tid;
char* id = new char[64];
snprintf(id, 64, "thread-%d", i);
int n = pthread_create(&tid, nullptr, routine, id);
if (n == 0)
tids.push_back(tid);
else
continue;
}
for (int i = 0; i < num; i++)
{
int n = pthread_join(tids[i], nullptr);
if (n == 0)
std::cout << "回收成功" << std::endl;
}
return 0;
}
手动申请id空间就能避免向同一块内存写数据的情况,当然申请的空间需要手动释放掉。
二. 线程id及进程地址空间布局
2.1线程id及进程地址空间布局

2.2 LWP和库中的线程联动

线程ID:线程ID是pthread库在创建线程时线程控制块(TCB)的起始虚拟地址
线程传参和返回值?
线程的返回值在执行结束后会写到TCB的result属性中
线程分离?
在线程的控制块TCB中还有一个int joinable的属性,线程创建时默认为1,线程退出时需要join等待回收;当线程被分离的时候,就会将这个属性写0,线程结束后线程TCB在用户空间会自动被释放。
Linux所有的线程都在库中实现,不同进程的线程之间不能互相访问,因为进程的地址空间不一样
事实上,线程创建的所有资源,其他同进程下的线程都能访问到,因为地址空间是相同的,只是为了数据干净,我们程序员控制线程不去访问其他线程的资源。
2.3 线程局部存储
我们知道,线程之间的数据也是共享的,只不过程序员控制着线程不去访问这些不属于该线程的数据。
线程的局部存储是指对于一个被__thread关键字修饰的全局变量,在每一个线程的空间内都开辟一块空间用于存储该变量,且线程之间互补可见,A线程对它做修改,B线程看不到。因此也就不存在并发问题。

