Linux(线程控制)
- [1 线程的相关函数](#1 线程的相关函数)
- [2 线程的创建](#2 线程的创建)
-
- [2.1 代码认识pthread_create函数](#2.1 代码认识pthread_create函数)
- [2.2 线程tid](#2.2 线程tid)
- [2.3 线程等待](#2.3 线程等待)
- [2.4 pthread_create的第4个参数和新线程返回值问题](#2.4 pthread_create的第4个参数和新线程返回值问题)
- [2.5 多线程代码](#2.5 多线程代码)
- [2.6 栈也是共享的,但是一般我们不会这么干](#2.6 栈也是共享的,但是一般我们不会这么干)
- [3 线程退出](#3 线程退出)
-
- [3.1 return](#3.1 return)
- [3.2 exit](#3.2 exit)
- [3.3 pthread_exit](#3.3 pthread_exit)
- [3.4 pthread_cancel(删掉一个线程)](#3.4 pthread_cancel(删掉一个线程))
- [4 主线程执行自己的事情](#4 主线程执行自己的事情)
- [5 线程进行程序替换](#5 线程进行程序替换)
1 线程的相关函数
(1)man pthread_create:

pthread_create它不是一个系统调用,它是一个库函数,在Linux内核中,没有线程的概念!只有LWP(轻量级进程),线程是用LWP模拟实现的,所以Linux系统不会给我们提供线程接口,只会提供创建轻量级进程的接口
这个创建轻量级进程的接口就是vfork:

(2)、上面这两个函数都是封装的clone这个系统调用,但是这个系统调用很难被使用

(3)、虽然Linux没有线程这个概念,但是作为我们使用者来说我们一直接触的都是线程,如果我们现在想在Linux下创建线程岂不是还要学习LWP这些概念吗?Linux的开发者为了方便用户的使用就新增了一层软件层,将LWP封装,我们将线程真正的提取出来是在用户层,而并非内核层,这种概念叫做用户级线程

(4)、因为windows真的有线程这种概念,所以windows下是真的有创建线程的系统调用
2 线程的创建
2.1 代码认识pthread_create函数
(1)在Linux中线程的库我们叫做pthread库(用户级),Linux系统自带!(原生线程库),以后我们创建线程都用pthread_create这个库函数

第一个参数:线程id
第二个参数:线程属性(这里我们不需要,统一设置为nullptr)
第三个参数:函数指针(线程执行)
第四个参数:我们给线程起的名字,这个参数就是我们传递给第三个函数指针的参数(回调函数),这个参数的类型为void*,意思是我们可以传递任意类型的参数
返回值:创建失败的原因
(2)创建线程的demo样例

2、makefile

ps -al | grep mythread(另一个终端查看该进程)
(3)C++也支持创建线程:

2.2 线程tid
如果我们将线程的tid打印出来:
(1)子线程打印自己的 tid(pthread_self)

哪一个线程调用该函数就返回该线程的tid

(2)新线程和main线程谁先运行,不确定,调度由OS自主决定,其次线程会瓜分进程的资源,时间片也会被瓜分
cpp
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <cstring>
#include <unistd.h>
std::string ToHex(pthread_t tid)
{
char buff[64];
snprintf(buff, sizeof(buff), "0x%lx", tid);
return buff;
}
//被重入
void *routine(void *args) // args就是pthread_create里的最后一个参数(线程名字)
{
std::string name = static_cast<const char *>(args);
while (1)
{
std::cout << "我是新线程,我的名字是:" << name << " tid:" << ToHex(pthread_self()) << std::endl;
sleep(1);
}
return 0;
}
int main()
{
pthread_t tid1;
pthread_create(&tid1, nullptr, routine, (void *)"thread-1");
pthread_t tid2;
pthread_create(&tid2, nullptr, routine, (void *)"thread-1");
pthread_t tid3;
pthread_create(&tid3, nullptr, routine, (void *)"thread-1");
pthread_t tid4;
pthread_create(&tid4, nullptr, routine, (void *)"thread-1");
pthread_t tid5;
pthread_create(&tid5, nullptr, routine, (void *)"thread-1");
std::cout << "new thread is " << ToHex(tid1) << std::endl;
std::cout << "new thread is " << ToHex(tid2) << std::endl;
std::cout << "new thread is " << ToHex(tid3) << std::endl;
std::cout << "new thread is " << ToHex(tid4) << std::endl;
std::cout << "new thread is " << ToHex(tid5) << std::endl;
while (1)
{
std::cout << "我是主线程" << std::endl;
sleep(1);
}
}
上面这一份代码就是我们创建多线程的代码示例,但是我们这里让所有线程执行同一个函数,只有参数的区别,打印的结果大家可以下来试一下(每一个线程的tid都不同),这符合我们的预期,但是我们之前学过线程重入的概念,这里不是发生重入了吗?
(3)在不加保护的情况下,显示器资源就是共享资源
(4)进程内的函数,线程共享,全局变量也是被所有线程共享
(5)如果一个线程崩溃,就会引起所有线程的崩溃,即进程崩溃
2.3 线程等待
(1)线程我们虽然不知道谁先运行,但我们必须保证主线程最后退出,并且线程也需要被等待和回收的,原因:1、可能会导致与僵尸进程类似的原因 2、为了知道该线程的执行结果

(1)参数一:指明哪一个新线程
(2)参数二:输出型参数 ,如果我们不关心退出结果就设为nullptr
(3)返回值:成功返回0,失败返回非0
(4)等待线程是要等待线程退出,那要是线程不退出?所以这个函数默认为阻塞等待

(2)上面这份代码中,子线程在运行,主线程在阻塞式等待
2.4 pthread_create的第4个参数和新线程返回值问题
(1)上面我们已经说了第四个参数是void*类型,意味着我们可以传递任何类型的参数,包括自定义类型,这意味着什么?我们可以在传参的同时让它帮我们完成一些任务,或者我们可以传递n个参数(封装为结构体或者类)

代码样例:
cpp
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <cstring>
#include <unistd.h>
class ThreadData
{
public:
ThreadData(const std::string name, int a, int b) : _name(name), _a(a), _b(b)
{
}
int Excute()
{
return _a + _b;
}
std::string Name()
{
return _name;
}
private:
std ::string _name;
int _a;
int _b;
};
void *routine(void *args) // args就是pthread_create里的最后一个参数(线程名字)
{
// std::string name = static_cast<const char *>(args);
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是:" << td->Name() << std::endl;
std::cout << "任务执行结果:" << td->Excute() << std::endl;
sleep(1);
break;
}
return 0;
}
int main()
{
pthread_t tid;
ThreadData *td = new ThreadData("thread-1", 1, 2);
pthread_create(&tid, nullptr, routine, (void *)td);
int n = pthread_join(tid, nullptr);
if (n != 0)
std::cout << "pthread_join error" << std::endl;
else
std::cout << "pthread_join success" << std::endl;
while (1)
{
std::cout << "我是主线程" << std::endl;
sleep(1);
}
}
执行结果:

(2)线程执行结束
1、线程入口函数return,表示线程退出(long long是因为void*是8字节的,转为int会有精度损失)

那我们现在思考一个问题,为什么thread_join的第二个返回值类型为void**,原因是当新线程退出时如果我们使用void类型,就返回不了(void它是没有大小的,OS根本不知道开辟多少空间给它),但是void*它是有大小的(32位为4字节,64位为8字节),为了能够成功传参这里采用的void**类型
2、新线程的返回值可以返回变量,数字,甚至是对象!(理论上堆空间也是共享的)

上面这个代码表示堆空间理论也是共享的,谁拿到堆空间的入口地址,谁就能访问堆空间!
3、总结:通过前面的学习,实际上进程地址空间除了栈,其他区其实都是共享的
2.5 多线程代码
cpp
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <cstring>
#include <unistd.h>
class ThreadData
{
public:
ThreadData() {}
void Init(const std::string &name, int a, int b)
{
_name = name;
_a = a;
_b = b;
}
void Excute()
{
_result = _a + _b;
}
int Result() { return _result; }
std::string Name() { return _name; }
void SetId(pthread_t tid) { _tid = tid; }
pthread_t Id() { return _tid; }
~ThreadData() {}
private:
std::string _name;
int _a;
int _b;
int _result;
pthread_t _tid;
};
void *routine(void *args) // args就是pthread_create里的最后一个参数(线程名字)
{
// std::string name = static_cast<const char *>(args);
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是:" << td->Name() << std::endl;
td->Excute(); // 让新线程去执行任务
sleep(1);
break;
}
return td;
}
#define NUM 10
int main()
{
ThreadData td[NUM];
// 1、准备处理我们要加工的数据
for (int i = 0; i < NUM; i++)
{
char id[64];
snprintf(id, sizeof(id), "thread-%d", i);
td[i].Init(id, i * 10, i * 20);
}
// 2、创建多线程
for (int i = 0; i < NUM; i++)
{
pthread_t id;
pthread_create(&id, nullptr, routine, &td[i]);
td[i].SetId(id);
}
// 3、等待多个线程
for (int i = 0; i < NUM; i++)
{
pthread_join(td[i].Id(), nullptr);
//这里我们可以获取退出信息,上面我们已经演示过了
}
// 4、汇总处理结果
for (int i = 0; i < NUM; i++)
{
printf("td[%d],result:[%d],id:[%ld]\n", i, td[i].Result(), td[i].Id());
}
while (1)
{
std::cout << "我是主线程" << std::endl;
sleep(1);
}
}
2.6 栈也是共享的,但是一般我们不会这么干

总结:所以线程会共享进程的所有进程地址空间内的所有区
3 线程退出
进程退出的方法
3.1 return
上面演示过了
3.2 exit

3.3 pthread_exit

demo样例:

3.4 pthread_cancel(删掉一个线程)
----- 取消一个线程,一定是该线程已经启动!

demo样例:

这里我们可以看到取消后拿到的返回值是-1,#define PTHREAD_CANCELED((void*) -1),所以取消线程也必须得join
4 主线程执行自己的事情
(1)上面我们使用join等待了线程的退出,但是这是阻塞式的等待,线程这里并没有非阻塞式的等待
---- 所以我们直接不等待新线程即可(将线程设置为分离状态)
(2)线程被等待的状态
1、joined:线程需要被join(默认)
2、detach:线程分离(主线程不需要等待新线程),类似于古时候的分家
3、注意:在多执行流情况下,主执行流一定是最后退的
(3)下面这个代码就是detach的demo,我们可以发现join的返回值为22,即出错返回

detach既可以在主线程中,又可以在新线程中
5 线程进行程序替换
线程其实是无法进行程序替换的,但是我们可以让新线程进行fork创建子进程