Linux系统——线程概念与控制之线程封装

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 🧮数据结构与算法 | 《数据结构与算法》 |
| 算法 | 《算法》 |
| C++ | 《C++》 |
| 🐧Linux | 《Linux系统》 |
| MySQL | 《MySQL》 |


目录

1.Linux线程概念

2.Linux进程vs线程

3.Linux线程控制

4.线程ID及进程地址空间布局

5.线程封装

6.附录


4.线程ID及进程地址空间布局

●pthread_create函数会产生一个线程ID,存放第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事

●前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程

●pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的

●线程库NPTL提供了pthread_self函数,可以获得线程自身的ID:

复制代码
pthread_t pthread_self(void);

pthread_t到底是什么类型?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址

5.线程封装

Thread.hpp

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <string>
#include <stdint.h>
#include <cstring>
#include <functional>

namespace ThreadSpace
{
    static uint32_t number = 1;

    class Thread
    {
        using func_t = std::function<void()>;

    private:
        void EnableRunning()
        {
            _isrunning = true;
        }

        // 写在类内会报错,因为成员函数有一个默认的this指针参数
        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);
            self->EnableRunning();
            if (self->_isdetach)
                self->Detach();
            pthread_setname_np(self->_tid, self->_name.c_str());
            self->_func(); // 回调处理

            return nullptr;
        }

    public:
        Thread(func_t func)
            : _tid(0), _isdetach(false), _isrunning(false), _ret(nullptr), _func(func)
        {
            _name = "thread-" + std::to_string(number++);
        }

        void EnableDetach()
        {
            _isdetach = true;
        }
        void Detach()
        {
            if (_isdetach)
                return;

            if (_isrunning)
            {
                int n = pthread_detach(_tid);
                if (n != 0)
                {
                    std::cerr << "detach error" << std::endl;
                    return;
                }

                EnableDetach();
                std::cout << "thread detach" << std::endl;
            }
        }

        void Start()
        {
            if(_isrunning) return ;

            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                std::cerr << "create error:" <<strerror(n)<< std::endl;
                return;
            }

            std::cout<<"create thread success"<<std::endl;
        }
        void Stop()
        {
            // 线程必须正在运行才能stop
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "cancel error" << std::endl;
                    return;
                }

                _isrunning = false;
                std::cout <<_name<<" stop" << std::endl;
            }
        }
        void Join()
        {
            if (_isdetach){
                std::cout<<"线程已经分离,不必等待"<<std::endl;
                return;
            }
            int n = pthread_join(_tid, &_ret);
            if (n != 0)
            {
                std::cerr << "join error" << std::endl;
                return;
            }

            std::cout << "wait thread success" << std::endl;
        }
        ~Thread() {}

    private:
        pthread_t _tid;
        std::string _name;
        bool _isdetach;
        bool _isrunning;
        void *_ret;
        func_t _func;
    };
}

Main.cc

cpp 复制代码
#include "Thread.hpp"
#include <unistd.h>
#include <vector>

using namespace ThreadSpace;

int main()
{
    std::vector<Thread> threads;
    for (int i = 0; i < 10; i++)
    {
        threads.emplace_back([]()
                 {
        while(true){
            char name[64];
            pthread_getname_np(pthread_self(),name,sizeof(name));
            std::cout<<"new thread,name:"<<name<<std::endl;
            sleep(1);
        } });
    }

    for(auto& t:threads)
        t.Start();

    for(auto& t:threads)
        t.Join();

    // Thread t([](){
    //     while(true){
    //         char name[64];
    //         pthread_getname_np(pthread_self(),name,sizeof(name));
    //         std::cout<<"new thread,name:"<<name<<std::endl;
    //         sleep(1);
    //     }
    // });

    // t.Start();
    // //t.Detach();

    // sleep(5);

    // //t.Stop();
    // //sleep(5);

    // t.Join();
    return 0;
}

●pthread_setname_np和pthread_getname_np是两个用于设置和获取线程名称的非标准函数(_np表示"non-portable",即非可移植的)。它们通常在Linu和其它一些类Unix系统中可以,用于调试和多线程程序的管理

●线程米高处长度限制:在Linux上,线程名称的最大长度为16个字段(包括结尾的\0)。若名字超过这个的长度,会被截断。

●权限:通常,只有线程自身可以设置自己的名称。尝试设置其它线程的名称可以会导致错误

bash 复制代码
// 运⾏结果查询
$ ps -aL
    PID LWP TTY TIME CMD
195828 195828 pts/1 00:00:00 main
195828 195829 pts/1 00:00:00 Thread-0
195828 195830 pts/1 00:00:00 Thread-1

模板形式的线程封装

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <string>
#include <stdint.h>
#include <cstring>
#include <functional>

namespace ThreadSpace
{
    static uint32_t number = 1;

    template <class T>
    class Thread
    {
        using func_t = std::function<void(T)>;

    private:
        void EnableRunning()
        {
            _isrunning = true;
        }

        // 写在类内会报错,因为成员函数有一个默认的this指针参数
        static void *Routine(void *args)
        {
            Thread<T> *self = static_cast<Thread<T> *>(args);
            self->EnableRunning();
            if (self->_isdetach)
                self->Detach();
            pthread_setname_np(self->_tid, self->_name.c_str());
            self->_func(self->_data); // 回调处理

            return nullptr;
        }

    public:
        Thread(func_t func,T data)
            : _tid(0)
            ,_isdetach(false)
            , _isrunning(false)
            , _ret(nullptr)
            , _func(func)
            ,_data(data)
        {
            _name = "thread-" + std::to_string(number++);
        }

        void EnableDetach()
        {
            _isdetach = true;
        }
        void Detach()
        {
            if (_isdetach)
                return;

            if (_isrunning)
            {
                int n = pthread_detach(_tid);
                if (n != 0)
                {
                    std::cerr << "detach error" << std::endl;
                    return;
                }

                EnableDetach();
                std::cout << "thread detach" << std::endl;
            }
        }

        void Start()
        {
            if(_isrunning) return ;

            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                std::cerr << "create error:" <<strerror(n)<< std::endl;
                return;
            }

            std::cout<<"create thread success"<<std::endl;
        }
        void Stop()
        {
            // 线程必须正在运行才能stop
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "cancel error" << std::endl;
                    return;
                }

                _isrunning = false;
                std::cout <<_name<<" stop" << std::endl;
            }
        }
        void Join()
        {
            if (_isdetach){
                std::cout<<"线程已经分离,不必等待"<<std::endl;
                return;
            }
            int n = pthread_join(_tid, &_ret);
            if (n != 0)
            {
                std::cerr << "join error" << std::endl;
                return;
            }

            std::cout << "wait thread success" << std::endl;
        }
        ~Thread() {}

    private:
        pthread_t _tid;
        std::string _name;
        bool _isdetach;
        bool _isrunning;
        void *_ret;
        func_t _func;
        T _data;
    };
}

6.附录

6.1源码阅读,理解线程(上)

以下是glibc-2.4中pthread源码相关内容:

路径:nptl/pthread_create.c

int __pthread_create_2_1(newthread, attr, start_routine, arg)

pthread_t *newthread;

const pthread_attr_t *attr;

void *(*start_routine)(void *);

void *arg;

{

STACK_VARIABLES;

// 重点1: 线程属性,虽然我们不设置,但是不妨碍我们了解

const struct pthread_attr *iattr = (struct pthread_attr *)attr;

if (iattr == NULL)

/* Is this the best idea? On NUMA machines this could mean

accessing far-away memory. */

iattr = &default_attr;

// 重点2:传说中的原⽣线程库中的⽤来描述线程的tcb

struct pthread *pd = NULL;

// 重点3: ALLOCATE_STACK会在先申请struct pthread对象,当然其实是申请⼀⼤块空间,

// struct pthread在空间的开头,⼀会追

int err = ALLOCATE_STACK(iattr, &pd);

if (__builtin_expect(err != 0, 0))

/* Something went wrong. Maybe a parameter of the attributes is

invalid or we could not allocate memory. */

versioned_symbol return err;

/* Initialize the TCB. All initializations with zero should be

performed in 'get_cached_stack'. This way we avoid doing this if

the stack freshly allocated with 'mmap'. */

#ifdef TLS_TCB_AT_TP

/* Reference to the TCB itself. */

pd->header.self = pd;

/* Self-reference for TLS. */

pd->header.tcb = pd;

#endif

/* Store the address of the start routine and the parameter. Since

we do not start the function directly the stillborn thread will

get the information from its thread descriptor. */

// 重点4:向线程tcb中设置未来要执⾏的⽅法的地址和参数

pd->start_routine = start_routine;

pd->arg = arg;

/* Copy the thread attribute flags. */

struct pthread *self = THREAD_SELF;

pd->flags = ((iattr->flags & ~(ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)) | (self->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)));

/* Initialize the field for the ID of the thread which is waiting

for us. This is a self-reference in case the thread is created

detached. */

pd->joinid = iattr->flags & ATTR_FLAG_DETACHSTATE ? pd : NULL;

/* The debug events are inherited from the parent. */

pd->eventbuf = self->eventbuf;

/* Copy the parent's scheduling parameters. The flags will say what

is valid and what is not. */

pd->schedpolicy = self->schedpolicy;

pd->schedparam = self->schedparam;

/* Copy the stack guard canary. */

#ifdef THREAD_COPY_STACK_GUARD

THREAD_COPY_STACK_GUARD(pd);

#endif

/* Copy the pointer guard value. */

#ifdef THREAD_COPY_POINTER_GUARD

THREAD_COPY_POINTER_GUARD(pd);

#endif

// ⼀堆参数设定,我们不关⼼

/* Determine scheduling parameters for the thread. */

if (attr != NULL && __builtin_expect((iattr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0, 0) && (iattr->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)) != 0)

{

INTERNAL_SYSCALL_DECL(scerr);

/* Use the scheduling parameters the user provided. */

if (iattr->flags & ATTR_FLAG_POLICY_SET)

pd->schedpolicy = iattr->schedpolicy;

else if ((pd->flags & ATTR_FLAG_POLICY_SET) == 0)

{

pd->schedpolicy = INTERNAL_SYSCALL(sched_getscheduler, scerr, 1, 0);

pd->flags |= ATTR_FLAG_POLICY_SET;

}

if (iattr->flags & ATTR_FLAG_SCHED_SET)

memcpy(&pd->schedparam, &iattr->schedparam,

sizeof(struct sched_param));

else if ((pd->flags & ATTR_FLAG_SCHED_SET) == 0)

{

INTERNAL_SYSCALL(sched_getparam, scerr, 2, 0, &pd->schedparam);

pd->flags |= ATTR_FLAG_SCHED_SET;

}

/* Check for valid priorities. */

int minprio = INTERNAL_SYSCALL(sched_get_priority_min, scerr, 1,

iattr->schedpolicy);

int maxprio = INTERNAL_SYSCALL(sched_get_priority_max, scerr, 1,

iattr->schedpolicy);

if (pd->schedparam.sched_priority < minprio || pd -

> schedparam.sched_priority > maxprio)

{

err = EINVAL;

goto errout;

}

}

/* Pass the descriptor to the caller. */

// 重点5:把pd(就是线程控制块地址)作为ID,传递出去,所以上层拿到的就是⼀个虚拟地址

*newthread = (pthread_t)pd;

/* Remember whether the thread is detached or not. In case of an

error we have to free the stacks of non-detached stillborn

threads. */

// 重点6: 检测线程属性是否分离,这个很好理解

bool is_detached = IS_DETACHED(pd);

/* Start the thread. */

err = create_thread(pd, iattr, STACK_VARIABLES_ARGS); // 重点函数

if (err != 0)

{

/* Something went wrong. Free the resources. */

if (!is_detached)

{

errout:

__deallocate_stack(pd);

}

return err;

}

return 0;

}

// 版本确认信息,意思就是如果⽤的库是GLIBC_2_1,pthread_create函数就是

__pthread_create_2_1

versioned_symbol(libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1);

线程属性:

struct pthread_attr

{

/* Scheduler parameters and priority. */

struct sched_param schedparam;

int schedpolicy;

/* Various flags like detachstate, scope, etc. */

int flags;

/* Size of guard area. */

size_t guardsize;

/* Stack handling. */

void *stackaddr;

size_t stacksize;

/* Affinity map. */

cpu_set_t *cpuset;

size_t cpusetsize;

};

线程tcb:

/* Thread descriptor data structure. */

struct pthread

{

union

{

#if !TLS_DTV_AT_TP

/* This overlaps the TCB as used for TLS without threads (see tls.h). */

tcbhead_t header;

#else

struct

{

int multiple_threads;

} header;

#endif

/* This extra padding has no special purpose, and this structure layout

is private and subject to change without affecting the official ABI.

We just have it here in case it might be convenient for some

implementation-specific instrumentation hack or suchlike. */

void *__padding16;

};

/* This descriptor's link on the `stack_used' or `__stack_user' list. */

list_t list;

/* Thread ID - which is also a 'is this thread descriptor (and

therefore stack) used' flag. */

pid_t tid;

/* Process ID - thread group ID in kernel speak. */

pid_t pid;

/* List of robust mutexes the thread is holding. */

#ifdef __PTHREAD_MUTEX_HAVE_PREV

__pthread_list_t robust_list;

#define ENQUEUE_MUTEX(mutex) \

do \

{ \

__pthread_list_t *next = THREAD_GETMEM(THREAD_SELF, robust_list.__next);

next->__prev = &mutex->__data.__list;

mutex->__data.__list.__next = next;

mutex->__data.__list.__prev = &THREAD_SELF->robust_list;

THREAD_SETMEM(THREAD_SELF, robust_list.__next, &mutex->__data.__list);

} while (0)

#define DEQUEUE_MUTEX(mutex) \

do \

{ \

mutex->__data.__list.__next->__prev = mutex->__data.__list.__prev;

mutex->__data.__list.__prev->__next = mutex->__data.__list.__next;

mutex->__data.__list.__prev = NULL;

mutex->__data.__list.__next = NULL;

}

while (0)

#else

__pthread_slist_t robust_list;

#define ENQUEUE_MUTEX(mutex) \

do \

{ \

mutex->__data.__list.__next = THREAD_GETMEM(THREAD_SELF, robust_list.__next); \

THREAD_SETMEM(THREAD_SELF, robust_list.__next, &mutex->__data.__list);

} while (0)

#define DEQUEUE_MUTEX(mutex) \

do \

{ \

__pthread_slist_t *runp = THREAD_GETMEM (THREAD_SELF,

robust_list.__next);

if (runp == &mutex->__data.__list)

THREAD_SETMEM(THREAD_SELF, robust_list.__next, runp->__next);

else

{

while (runp->__next != &mutex->__data.__list)

runp = runp->__next;

runp->__next = runp->__next->__next;

mutex->__data.__list.__next = NULL;

}

}

while (0)

#endif

/* List of cleanup buffers. */

struct _pthread_cleanup_buffer *cleanup;

/* Unwind information. */

struct pthread_unwind_buf *cleanup_jmp_buf;

#define HAVE_CLEANUP_JMP_BUF

/* Flags determining processing of cancellation. */

int cancelhandling;

/* Bit set if cancellation is disabled. */

#define CANCELSTATE_BIT 0

#define CANCELSTATE_BITMASK 0x01

/* Bit set if asynchronous cancellation mode is selected. */

#define CANCELTYPE_BIT 1

#define CANCELTYPE_BITMASK 0x02

/* Bit set if canceling has been initiated. */

#define CANCELING_BIT 2

#define CANCELING_BITMASK 0x04

/* Bit set if canceled. */

#define CANCELED_BIT 3

#define CANCELED_BITMASK 0x08

/* Bit set if thread is exiting. */

#define EXITING_BIT 4

#define EXITING_BITMASK 0x10

/* Bit set if thread terminated and TCB is freed. */

#define TERMINATED_BIT 5

#define TERMINATED_BITMASK 0x20

/* Bit set if thread is supposed to change XID. */

#define SETXID_BIT 6

#define SETXID_BITMASK 0x40

/* Mask for the rest. Helps the compiler to optimize. */

#define CANCEL_RESTMASK 0xffffff80

#define CANCEL_ENABLED_AND_CANCELED(value) \

(((value) & (CANCELSTATE_BITMASK | CANCELED_BITMASK | EXITING_BITMASK

| CANCEL_RESTMASK | TERMINATED_BITMASK)) == CANCELED_BITMASK)

#define CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS(value) \

(((value) & (CANCELSTATE_BITMASK | CANCELTYPE_BITMASK | CANCELED_BITMASK

\

| EXITING_BITMASK | CANCEL_RESTMASK | TERMINATED_BITMASK)) \

== (CANCELTYPE_BITMASK | CANCELED_BITMASK))

/* We allocate one block of references here. This should be enough

to avoid allocating any memory dynamically for most applications. */

struct pthread_key_data

{

/* Sequence number. We use uintptr_t to not require padding on

32- and 64-bit machines. On 64-bit machines it helps to avoid

wrapping, too. */

uintptr_t seq;

/* Data pointer. */

void *data;

} specific_1stblockPTHREAD_KEY_2NDLEVEL_SIZE;

/* Two-level array for the thread-specific data. */

struct pthread_key_data *specificPTHREAD_KEY_1STLEVEL_SIZE;

/* Flag which is set when specific data is set. */

bool specific_used;

/* True if events must be reported. */

bool report_events;

/* True if the user provided the stack. */

bool user_stack;

/* True if thread must stop at startup time. */

bool stopped_start;

/* Lock to synchronize access to the descriptor. */

lll_lock_t lock;

/* Lock for synchronizing setxid calls. */

lll_lock_t setxid_futex;

#if HP_TIMING_AVAIL

/* Offset of the CPU clock at start thread start time. */

hp_timing_t cpuclock_offset;

#endif

/* If the thread waits to join another one the ID of the latter is

stored here.

In case a thread is detached this field contains a pointer of the

TCB if the thread itself. This is something which cannot happen

in normal operation. */

struct pthread *joinid;

/* Check whether a thread is detached. */

#define IS_DETACHED(pd) ((pd)->joinid == (pd))

/* Flags. Including those copied from the thread attribute. */

int flags;

/* The result of the thread function. */

// 线程运⾏完毕,返回值就是void*, 最后的返回值就放在tcb中的该变量⾥⾯

// 所以我们⽤pthread_join获取线程退出信息的时候,就是读取该结构体

// 另外,要能理解线程执⾏流可以退出,但是tcb可以暂时保留,这句话

void *result;

/* Scheduling parameters for the new thread. */

struct sched_param schedparam;

int schedpolicy;

/* Start position of the code to be executed and the argument passed

to the function. */

// ⽤⼾指定的⽅法和参数

void *(*start_routine)(void *);

void *arg;

/* Debug state. */

td_eventbuf_t eventbuf;

/* Next descriptor with a pending event. */

struct pthread *nextevent;

#ifdef HAVE_FORCED_UNWIND

/* Machine-specific unwind info. */

struct _Unwind_Exception exc;

#endif

/* If nonzero pointer to area allocated for the stack and its

size. */

// 线程⾃⼰的栈和⼤⼩

void *stackblock;

size_t stackblock_size;

/* Size of the included guard area. */

size_t guardsize;

/* This is what the user specified and what we will report. */

size_t reported_guardsize;

/* Resolver state. */

struct __res_state res;

/* This member must be last. */

char end_padding\[\];

#define PTHREAD_STRUCT_END_PADDING \

(sizeof(struct pthread) - offsetof(struct pthread, end_padding))

}

__attribute((aligned(TCB_ALIGNMENT)));

相关推荐
叼烟扛炮16 分钟前
[LangChain] 学习笔记(05):提示词模板(Prompt)与少样本注入(Few-Shot)实战
linux·人工智能·笔记·学习·langchain·prompt
CodeYp201919 分钟前
第5讲:Socket 编程 TCP
linux·socket·tcp
咔_29 分钟前
有IPv6地址,为什么外面还是连不进来?让同一台服务分别卡在四个位置
linux·计算机网络
CodeYp20191 小时前
第3讲:网络命令
linux·网络·命令
一个天蝎座 白勺 程序猿9 小时前
KingbaseES数据库归档重做日志文件管理:从配置到运维的实战指南
运维·数据库·oracle·kingbasees·金仓数据库
天诰12 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
2035去旅行13 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
分布式存储与RustFS13 小时前
镜像仓库的层别堆本地盘:用 RustFS 给 Docker Registry 当 S3 后端
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
陈希12314 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器