Linux----线程控制
这里写目录标题
1. Linux线程控制
1. ps -aL:查看Linux操作系统的所有线程
lwp:light weight process:轻量级线程,所以进程调度时操作系统看lwp和pid就可以分清进程和线程,如果切换task_struct时,task_struct里的pid和上次被调度的进程的pid相同,但是lwp变了就是线程调度(轻量级进程调度),如果task_struct里的lwp和上次被调度的进程的pid不同就是进程调度
进程调度的时间片是均分给线程的,异常之后任意一个线程崩溃,进程就崩溃了,所以健壮性低

pthread_create是创建线程的接口,传入rout函数名,新创建的线程就会执行那个函数方法,此时线程就可以在同一个虚拟地址空间访问不同函数了,函数内部都是虚拟地址,都是资源,所以代码区不需要我们划分,只要有函数入口地址,线程天然就可以访问同一个虚拟地址空间代码区的不同资源了
7.1 pthread库

Linux操作系统只有轻量级进程的概念,没有线程的概念,所以pthread库是封装给用户用的,右面是创建轻量级进程的系统调用,所以在要-l指明库,因为这个库不是c语言标准库,否则链接报错
当我们加上-l 链接成功后,运行该可执行程序发现消息混乱,因为多线程都是向显示器文件打印,此时显示器文件就是共享资源,会有信息安全的问题,就像命名管道一样
7.2 C++线程库

所以C++也要-l指明pthread库
7.3 Linux线程控制接口
1. 创建线程

成功0被返回,失败错误码被设置
1. 第1个参数:输出型参数,得到线程id
2. 第2个参数:线程属性,一般设置为nullptr,默认
3. 新线程要执行的函数入口,一个函数指针
4. 传递给函数指针的参数
2. 等待线程

void fun()
{
printf("666\n");
}
void *rout(void *arg)
{
int n=1;
while (n--)
{
// fun();
printf("I am thread 1\n");
sleep(1);
}
return nullptr;
}
int main(void)
{
pthread_t tid;
int ret=1;
pthread_create(&tid, NULL, rout, NULL);
while (ret--)
{
//fun();
printf("I'am main thread2\n");
sleep(1);
}
pid_t n=pthread_join(tid,nullptr);
printf("tid:%ld\n",tid);
}
新线程创建好,要被主线程等待(main函数里的线程),否则类似僵尸进程问题,内存泄漏,主进程join就可以解决内存泄漏问题
这个fun函数可以被主线程和新线程并行访问,此时称这个函数被重入了,但是没有出现问题,所以这个函数是可重入函数
pid_t n=pthread_join(tid,nullptr);
主线程可以拿到新线程的tid,但这个tid是一个无符号长整数,并不是lwp,它也不该是lwp,因为轻量级进程是Linux操作系统的概念,但用户只认线程,所以不能一样,lwp是用来区分调度的
3.线程终止


1. 线程不能用exit(int)终止,因为这是终止进程的方法,一个线程调用所以线程就都没了
2. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
3. 线程可以调用pthread_exit终止自己
4. ⼀个线程可以调用pthread_cancel终止同一进程中的另⼀个线程,一般是主线程终止新建线程,但是一定要保证新线程已经启动,要不你取消啥呢
4. 分离线程

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则
无法释放资源,从而造成系统泄漏,但是进程那里可以使用SIGCHLD,SIG_IGN,可以让父进程不用等待子进程,OS自动释放子进程的资源,线程也可以,可以主线程分离新线程:pthread_detach(tid);
也可以新线程分离自己:pthread_detach(pthread_self());
此时主线程无需等待新线程,新线程的资源被OS自动回收
joinable和分离是冲突的,⼀个线程不能既是joinable又是分离的,所以分离后join一定会失败,
判断join的返回值!=0证明等待失败分离成功
5. pthread_self

谁调用这个函数就获取谁的tid
void *rout(void *arg)
{
int n = 1;
while (n--)
{
// fun();
printf("I am thread 1\n");
sleep(1);
}
pid_t s = pthread_self();
printf("tid1:0x%x\n", s); // 新线程
return nullptr;
}
int main(void)
{
pthread_t tid;
int ret = 1;
pthread_create(&tid, NULL, rout, NULL);
while (ret--)
{
// fun();
printf("I'am main thread2\n");
sleep(1);
}
pid_t n = pthread_join(tid, nullptr);
pid_t s = pthread_self();
printf("tid1:0x%x\n", tid); // 新线程
printf("tid:0x%x\n", s); // 主线程
}

6. 主线程可以拿到新线程的退出码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <iostream>
using namespace std;
void fun()
{
printf("666\n");
}
void *rout(void *arg)
{
int n = 1;
while (n--)
{
// fun();
printf("I am thread\n");
sleep(1);
}
pid_t s = pthread_self();
printf("tid1:0x%x\n", s); // 新线程
return (void *)100;
}
int main(void)
{
pthread_t tid;
//int ret = 1;
int a=1;
pthread_create(&tid, NULL, rout, NULL);
while (a--)
{
// fun();
printf("I'am main thread2\n");
sleep(1);
}
void *ret = nullptr;
pid_t n = pthread_join(tid, &ret);
cout << (long long int)ret << endl;
return 0;
}

新线程返回的是新线程的退出码,主线程可以拿到,第二个参数是输出型参数返回的是void的地址,用void接收,相当于你要传&ret,内部才能void对你的ret进行修改,否则就是值拷贝,修改不了ret,所以必须传&ret,然后才能修改,但是void类型不能解引用,所以必须强转成longlong类型,ret就是100强转后的地址,在强转回来就可以拿到退出码
为什么线程没有退出信号或core dump呢?
因为线程一旦异常进程就会挂,所有线程都挂了,拿到也没意义,也拿不到,因为你不知道哪个线程出的错,只能知道进程出错了,所以信号是进程要处理的话题,join都是基于线程健康跑完的情况,不需要处理异常信号
主线程和新线程谁先运行不确定,跟进程一样
2. pthread库的深刻理解

库也是ELF文件要加载到内存,映射到虚拟地址空间,然后我们写的代码就可以访问库的任意方法了,线程的概念是库维护的,在库内部就一定会存在多个被创建好的线程,库要先描述再组织的管理线程,所以当我们调用pthread_create时,库就会在内部把这些东西创建好,并维护类似于这样的数据结构
所以Linux所有线程都在库中
优先级,时间片,上下文是进程的概念,进程内部lwp会保存,所以线程一部分在库里描述,一部分在进程内部描述

这就是pthread库在虚拟地址空间创建的数据块,内部主要包含这些,struct_pthread内部包含tid,但其实tid就是线程在库中对应数据块的起始虚拟地址,就是struct pthread的地址,每个线程都要有自己独立的栈空间,这三个部分构成TCB,所以线程需要join,如果不join,TCB无法释放内存泄漏,struct_pthread内部有voidret,当一个新线程运行完返回return (void)100,其实是返回给内部的ret,然后在传给我们join的&s的,因为join可以拿到这个数据块的其实虚拟地址,所以可以访问到ret

所以当我们调用pthread_create时,库会在库内部创建线程控制的管理块,也要在内核中,调用创建轻量级进程的系统调用,执行什么方法用户自己传,栈是库内部自己创建的,再把栈传过去,所以进程在调度时产生的临时数据就可以放到栈里了,轻量级进程调用routine时会形成栈帧,就是形成在自己的栈结构里,就是库传入的stack地址
其中n就是临时数据,但其实主线程是可以访问新线程的栈的,一个线程的所有资源都可以被另一个线程看到,因为线程共享虚拟地址空间,每个线程都有自己独立的栈,因为我们一般不会让一个线程去访问另一个线程的栈

setname:设置名字,它是线程局部存储,其实就是把字符串写到线程局部存储里
getname:得到名字,直接从刚才写入的线性局部存储里拿字符串,只有你一个人访问,不存在并发问题,因为他要tid
线程共享资源,所以是同一个全局变量做修改,但是如果不想让多个线程对同一个全局变量修改,就可以在全局变量前面加上__thread int count,此时相当于在多个线程的线程局部存储里创建该变量,就是每个线程都有自己的count,此时虚拟地址不同,访问时就各自从线程局部存储访问自己的count变量,不会对同一个变量访问了
线程局部存储只能存储内置类型和部分指针
3. 封装线程
main.cc
#include "thread.hpp"
using namespace ThreadModlue;
#include <unistd.h>
#include<vector>
int main()
{
Thread t([]()
{
while (true)
{
char name[128];
std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
sleep(1);
} });
t.start();
t.Detach();
sleep(5);
t.stop();
sleep(5);
t.join();
// std::vector<Thread> threads;
// for (int i = 0; i < 10; i++)
// {
// threads.emplace_back([]()
// {
// while(true)
// {
// char name[128];
// std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
// sleep(1);
// } });
// }
// for (auto &thread : threads)
// {
// thread.start();
// }
// for (auto &thread : threads)
// {
// thread.join();
// }
return 0;
}
Makefile
thread:main.cc
g++ -o $@ $^ -l pthread -std=c++11
.PHONY:clean
clean:
rm -f thread
Thread.hpp
#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue
{
static uint32_t number = 1;
class Thread
{
private:
using func_t = function<void()>;
void EnableDetach()
{
cout << "线程被分离了" << endl;
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
static void *routine(void *args) // 必须静态否则多一个参数
{
Thread *self = static_cast<Thread *>(args);
self->EnableRunning();
if (self->_isdetach)
self->Detach();
self->_func(); // 回调
return nullptr;
}
public:
Thread(func_t func)
: _tid(0), _isdetach(false), _isrunning(false), res(nullptr), _func(func)
{
_name = "thread-" + to_string(number++);
}
void Detach()
{
if (_isdetach)
{
return;
}
if (_isrunning)
{
pthread_detach(_tid);
}
EnableDetach();
}
bool start()
{
if (_isrunning)
{
return false;
}
int n = pthread_create(&_tid, nullptr, routine, this); // 需要传this来回调
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << _name << " create success" << std::endl;
return true;
}
}
bool stop()
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cerr << "stop thread error: " << strerror(n) << std::endl;
return false;
}
else
{
_isrunning = false;
std::cout << _name << " stop" << std::endl;
return true;
}
}
return false;
}
void join()
{
if (_isdetach)
{
std::cout << "你的线程已经是分离的了,不能进行join" << std::endl;
return;
}
else
{
int n = pthread_join(_tid, &res);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
}
else
{
std::cout << "join success" << std::endl;
}
}
}
~Thread()
{
}
private:
string _name;
pthread_t _tid;
bool _isdetach;
bool _isrunning;
void *res;
func_t _func;
};
}
更多推荐




所有评论(0)