💁♂️个人主页:进击的荆棘
👇作者其它专栏:
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 🧮数据结构与算法 | 《数据结构与算法》 |
| 算法 | 《算法》 |
| 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;
};
}
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)));