muduo学习笔记5——线程封装

  • 线程标识符

  • pthread_self

  • gettid

  • __thread, gcc内置的线程局部存储设施

  • __thread只能修饰POD类型, plain old data

  • pthread_atfork

线程标识符

Linux中,每个进程有一个pid,类型为pid_t,由getpid()取得。Linux下的POSIX线程也有一个id,类型为pthread_t,由pthread_self()取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP:轻量级进程),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。

有时候我们可能需要知道线程的真实pid。比如进程P1要向另外一个进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread id,而只能使用该线程的真实pid,称为tid。
函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取

return syscall(SYS_gettid)

但因为使用线程调用开销很大,所以我们需要对所获取的tid做一个缓存,防止每次都使用系统调用,从而提高获取tid的效率

muduo库中线程类的实现,采用的是基于对象的方式来实现的
在这里插入图片描述

started_表示线程是否已经启动
pthreadId_:进程中线程id
tid_真实线程id
func_该线程要回调的函数
name_线程名称
numCreated已经创建的线程个数,是一个原子整数类

代码还是很清楚的, Thread.cc
在这里插入图片描述
其中CurrentThread中实现了对线程真实tid的缓存
在这里插入图片描述
注意一下__thread修饰的变量是线程局部存储的, 且只能修饰POD类型,不能修饰class类型,因为无法自动调用构造函数和析构函数
在这里插入图片描述

__thread, gcc内置的线程局部存储设施

POD类型 (plain old data),与C兼容的原始数据,例如结构和整型等C语言中的类型是POD类型,但带有用户定义的构造函数或虚函数的类则不是

__thread string t_obj1(“hello”);    // 错误,不能调用对象的构造函数
__thread string* t_obj2 = new string;   // 错误,指针类型是POD类型,但是初始化必须用编译期常量,不能是运行期的
__thread string* t_obj3 = NULL; // 正确,但是需要手工初始化并销毁对象

__thread变量在每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。用一个例子来理解它的用法。

#include <pthread.h>
#include <iostream>
#include <unistd.h>

using namespace std;

//__thread int var = 5;
int var = 5;

void *worker1(void* arg);
void *worker2(void* arg);

int main()
{
        pthread_t p1, p2;

        pthread_create(&p1, NULL, worker1, NULL);
        pthread_create(&p2, NULL, worker2, NULL);
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);

        return 0;
}

void *worker1(void* arg)
{
        cout << ++var << endl;
}

void *worker2(void* arg)
{
        cout << ++var << endl;
}

/**
 * 使用__thread关键字,输出为: 
 *                         6
 *                         6 个线程的值互不干扰, 跟全局变量?
 * 
 * 不使用__thread关键字,输出为:
 *                         6   
 *                         7
 * /

pthread_atfork()函数

在这里插入图片描述

#include <pthread.h>
int pthread_atfork(void (*prepare)(void), 
                    void (*parent)(void), 
                    void (*child)(void));

在调用fork时,内部创建子进程之前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child这就是pthread_atfork()的作用, 因为在fork之前可能有多个线程,有可能是在主线程中调用也有可能是在子线程中调用, 如果fork()是在子线程中调用得到一个新进程,新进程就只有一个执行序列,只有一个线程(调用fork的线倍继承下来),用pthread_atfork()来可以避免这种情况

对于编写多线程程序来说,最好不要调用fork(),即不要编写多线程多进程程序,因为Linux的fork()只克隆当前线程的thread of control, 不克隆其他线程。 fork()之后,除了当前线程之外,其他线程都消失了,也就是说,不能够一下子fork()和父进程一样的多线程子进程

fork()之后子进程中只有一个线程,其他线程都小时了,这就曹正一个危险的局面:其他线程可能正好位于临界区指内,持有了某个锁,而它忽然死亡,再也没有机会去解锁了。如果子进程试图再对同一个mutex加锁,就会死锁

那我们调用atfork()的话就可以在fork之前,即创建子进程之前去解锁,那样拷贝下来的就是解锁的,就不会死锁了

∴ 父进程在创建一个子进程的时候,指挥复制当前线程的执行状态,其他线程不会复制,因此子进程会处于死锁的状态

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值