Linux线程控制封装及线程互斥

1.clone函数的使用

#define _GNU_SOURCE

#include <sched.h>

#include <signal.h>

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);

fn:子进程或线程的入口函数

child_stack:子进程的栈地址,通常需要手动分配,栈的大小需要足够容纳子进程变量的局部变量和函数调用。

  • flags:控制子进程或线程共享哪些资源(如内存、文件描述符等)。常见的标志包括:

    • CLONE_VM:共享内存空间。

    • CLONE_FS:共享文件系统信息。

    • CLONE_FILES:共享文件描述符。

    • CLONE_SIGHAND:共享信号处理机制。

    • CLONE_THREAD:让子进程成为与父进程同一线程组的线程。

    • CLONE_NEWNS:创建新的 mount namespace。

    • CLONE_NEWUTS:创建新的 UTS namespace,用于隔离主机名。

    • arg :传递给 fn 的参数。

代码示例

stack+STACK_SIZE是因为开辟的栈区使用是从高到低的,开辟栈区是从低地址到高地址,开辟后返回的地址是低地址,要用高地址使用,开辟的地址加上size就是使用的地址。

cpp 复制代码
#include <sched.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>

#define STACK_SIZE (1024*1024) //1MB的栈空间
//进程间通信,传递退出码
//子进程执行的函数
static int child_func(void* arg)
{
    while(true)
    {
        printf("Child process:PID=%d\n",getpid());
        sleep(1);
    }
    return 0;
}
int main()
{
    char* stack=(char*)malloc(STACK_SIZE);//为子进程分配栈空间
    if(stack==NULL)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    //使用clone创建子进程
    pid_t pid=clone(child_func,stack+STACK_SIZE,CLONE_VM|SIGCHLD,NULL);
    if(pid==-1)
    {
        perror("clone");
        free(stack);
        exit(EXIT_FAILURE);
    }
    printf("Parent process:PID=%d,child PID=%d\n",getpid(),pid);

    //等待子进程结束
    if(waitpid(pid,NULL,0)==-1)
    {
        perror("waitpid");
        free(stack);
        exit(EXIT_FAILURE);
    }
    free(stack);
    

    return 0;
}

注意:虽然栈空间是独立的,但是线程的地址都是一起的,所以可以访问其它线程的栈空间

代码示例

可以在主线程看到创建线程的变量,另外在创建的新线程死循环会报段错误,因为线程的调度和切换,操作系统可能会回收栈空间,这样就会把a回收,主线程就无权限访问了。

cpp 复制代码
#include <sched.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>

int* p=nullptr;

void* threadrun(void* args)
{
    int a=123;
    p=&a;
    //while(true) {sleep(1);}
}


int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadrun,nullptr);
    while(true)
    {
        std::cout<<"*p:"<<*p<<std::endl;
        sleep(1);
    }

    pthread_join(tid,nullptr);

    return 0;
}

代码示例二

Thread.hpp文件

cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>

namespace ThreadMoudle
{
    static int number = 1; // bug

    class Thread
    {
        //把MAIN的方法进行包装
        using func_t =std::function<void()>;
        private:
            void EnableDetach()
            {
                std::cout<<"线程被分离"<<std::endl;
                _isdetach=true;
            }
            void EnableRunning()
            {
                _isrunning=true;
            }
            //传的是this指针,所以是thread*类型
            static void* Routine(void* args)//属于类内的成员函数,默认包含this指针,会多一个参数导致参数不匹配
            {//这里会把私有成员进行改变,因为执行流已经开始了,分离状态和运行状态此刻要确定好
                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),
                _res(nullptr),
                _func(func)
                {
                    _name="thread-"+std::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);
                    if(n!=0)
                    {
                        std::cerr<<"create pthread 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<<"cancel 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;
                    }
                    int n=pthread_join(_tid,&_res);
                    if(n!=0)
                    {
                        std::cerr<<"fail"<<strerror(n)<<std::endl;
                    }
                    else
                    {
                        std::cout<<"join success"<<std::endl;
                    }
                }
                ~Thread()
                {}

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

MAIN.cc文件

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

using namespace ThreadMoudle;


int main()
{
    // std::vector<Thread> threads;
    // for(int i=0;i<10;i++)
    // {
    //     threads.emplace_back([](){
    //         while(true)
    //         {
    //             char name[128];
    //             pthread_getname_np(pthread_self(),name,sizeof(name));
    //             std::cout<<"我是一个新线程"<<name<<std::endl;//线程名字
    //             sleep(1);

    //         }
    //     });
    // }
    // for(auto& thread:threads)
    // {
    //     thread.Start();
    // }
    // for(auto& thread:threads)
    // {
    //     thread.Join();
    // }
    //传递执行的任务
    Thread t([](){
        while(true)
        {
            char name[128];
            pthread_getname_np(pthread_self(),name,sizeof(name));
            std::cout<<"我是一个新线程:"<<name<<std::endl;
            sleep(1);
        }

    });
    t.Start();
    //t.Detach();
    sleep(5);
    t.Stop();
    sleep(5);
    t.Join();


    return 0;
}

pthread_setname_np函数

cpp 复制代码
#define _GNU_SOURCE
#include <pthread.h>

int pthread_setname_np(pthread_t thread, const char *name);
  • thread:要设置名称的线程的 ID。

  • name :要设置的线程名称,长度限制为 16 个字符(包括终止符 \0)。

返回值

  • 成功时返回 0。

  • 出错时返回非零错误码。

错误码

  • ERANGEname 的长度超过了允许的限制。

pthread_getname_np函数

cpp 复制代码
#define _GNU_SOURCE
#include <pthread.h>

int pthread_getname_np(pthread_t thread, char *name, size_t len);

参数

  • thread:要获取名称的线程的 ID。

  • name:一个字符数组,用于存储获取到的线程名称。

  • lenname 数组的长度。

返回值

  • 成功时返回 0。

  • 出错时返回非零错误码:

    • EINVALlen 小于 1 或大于 NAME_MAX(通常是 16)。

    • ESRCH:指定的线程不存在。

你提到的问题非常关键,这是 C++ 中多线程编程时常见的一个陷阱。在 C++ 中,类的成员函数默认有一个隐含的 `this` 指针参数,这会导致成员函数的签名与 `pthread_create` 或其他线程库要求的回调函数签名不匹配。

你正确地使用了 `static` 关键字来定义 `Routine` 函数。这是因为:

  1. **非静态成员函数**:非静态成员函数会隐含一个 `this` 指针作为第一个参数,这会导致函数签名与 `pthread_create` 需要的回调函数签名不匹配。

  2. **静态成员函数**:静态成员函数没有 `this` 指针,因此可以作为线程回调函数。

关键点解释

  1. **静态成员函数作为回调**:
  • `Routine` 是一个静态成员函数,没有隐含的 `this` 指针。

  • 它的签名与 `pthread_create` 需要的回调函数签名完全匹配:`void* (*)(void*)`。

**2. **传递 `this` 指针**:

  • 在调用 `pthread_create` 时,将 `this` 指针作为参数传递给 `Routine`。
  • 在 `Routine` 中,使用 `static_cast<Thread*>(args)` 将 `void*` 参数转换回 `Thread*` 类型。**
  1. **线程生命周期管理**:
  • 在 `Thread` 的构造函数中调用 `pthread_create` 创建线程。

  • 在 `Thread` 的析构函数中调用 `pthread_join` 等待线程结束,确保线程安全退出。

总结

你提到的问题非常关键,C++ 的成员函数由于隐含的 `this` 指针,不能直接用作线程回调函数。通过将回调函数定义为静态成员函数,并将 `this` 指针作为参数传递,可以解决这个问题。希望这个解决方案对你有帮助!

多线程创建执行任务

把方法放到vector里面存储,然后范围foe逐一执行任务。

cpp 复制代码
#include "thread.hpp"
#include <unistd.h>
#include <vector>
#include <iterator>  // 或 <algorithm>
#include<algorithm>
using namespace ThreadMoudle;

int main()
{
    std::vector<Thread> threads;
    for (int i = 0; i < 10; i++)
    {
        threads.emplace_back([]()
                             {
        while(true)
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
            sleep(1);
        } });
    }
    for (auto &thread : threads)
    {
        thread.Start();
    }

    for (auto &thread : threads)
    {
        thread.Join();
    }

    // Thread t([](){
    //     while(true)
    //     {
    //         char name[128];
    //         pthread_getname_np(pthread_self(), name, sizeof(name));
    //         std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
    //         sleep(1);
    //     }
    // });
    // t.Start();
    // t.Detach();
    // sleep(5);

    // t.Stop();

    // sleep(5);

    // t.Join();

    return 0;
}

模板参数形式

Thread.cpp文件

cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>

namespace ThreadModlue
{
    static uint32_t number = 1; // bug

    template<typename T>
    class Thread
    {
        using func_t = std::function<void(T)>; // 暂时这样写,完全够了
    private:
        void EnableDetach()
        {
            std::cout << "线程被分离了" << std::endl;
            _isdetach = true;
        }
        void EnableRunning()
        {
            _isrunning = true;
        }
        static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
        {
            Thread<T> *self = static_cast<Thread<T> *>(args);
            self->EnableRunning();
            if (self->_isdetach)
                self->Detach();
            self->_func(self->_data); // 回调处理

            return nullptr;
        }
        // bug
    public:
        Thread(func_t func, T data)
            : _tid(0),
              _isdetach(false),
              _isrunning(false),
              res(nullptr),
              _func(func),
              _data(data)
        {
            _name = "thread-" + std::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);
            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 << "create 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;
            }
            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:
        pthread_t _tid;
        std::string _name;
        bool _isdetach;
        bool _isrunning;
        void *res;
        func_t _func;
        T _data;
    };
}

#endif

Main.cc文件

可以传递多类型参数执行任务,传递对象,可以设置多个成员,这样执行完后,就可以把得到执行完后的成员变量的值。

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

using namespace ThreadModlue;

// 我们可以传递对象吗???
// class ThreadData
// {
// public:
//     pthread_t tid;
//     std::string name;
// };

// void Count(ThreadData td)
// {
//     while (true)
//     {
//         std::cout << "我是一个新线程" << std::endl;
//         sleep(1);
//     }
// }

int main()
{
    // ThreadData td;
    // Thread<ThreadData> t(Count, td);

    // t.Start();

    // t.Join();

    // Thread t([](){
    //     while(true)
    //     {
    //         std::cout << "我是一个新线程" << std::endl;
    //         sleep(1);
    //     }
    // });
    // t.Start();
    // t.Detach();
    // sleep(5);

    // t.Stop();

    // sleep(5);

    // t.Join();

    return 0;
}

线程局部存储

两个线程,一个打印并改变变量,一个只打印,可以看到变量是一起改变的,所以要想这个变量不被其它线程看到,就需要加__thread前缀,线程局部存储也有限制,只能存储内置类型和部分指针。

代码示例

cpp 复制代码
#include <pthread.h>
#include <iostream>
#include <string>
#include <unistd.h>

// 该count叫做线程的局部存储!
__thread int count = 1;
// 线程局部存储有什么用?全局变量,我又不想让这个全局变量被其他线程看到!
// 线程局部存储,只能存储内置类型和部分指针

std::string Addr(int &c)
{
    char addr[64];
    snprintf(addr, sizeof(addr), "%p", &c);
    return addr;
}

void *routine1(void *args)
{
    (void)args;
    while (true)
    {
        std::cout << "thread - 1, count = " << count << "[我来修改count], "
                  << "&count: " << Addr(count) << std::endl;
        count++;
        sleep(1);
    }
}

void *routine2(void *args)
{
    (void)args;
    while (true)
    {
        std::cout << "thread - 2, count = " << count
                  << ", &count: " << Addr(count) << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, nullptr, routine1, nullptr);
    pthread_create(&tid2, nullptr, routine2, nullptr);

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

2.线程互斥

进程线程间的互斥相关背景概念

临界资源:多线程执行流共享的资源就叫临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性:不会被任何调度机制打断的操作,该操作只用两种状态,要么完成,要么未完成

互斥量mutex

互斥量是一种同步原语,用于保护共享资源,防止多个线程同时访问。在多线程环境中,互斥量可以确保同一时间只有一个线程可以访问特定的资源,从而避免数据竞争和未定义行为。

代码示例

模拟抢票,因为有了锁,所以不会出现票为负数的情况

cpp 复制代码
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int ticket = 1000;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *route(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        pthread_mutex_lock(&lock);
        if (ticket > 0) // 1. 判断
        {
            usleep(1000);                               // 模拟抢票花的时间
            printf("%s sells ticket:%d\n", id, ticket); // 2. 抢到了票
            ticket--;                                   // 3. 票数--
            pthread_mutex_unlock(&lock);
        }
        else
        {
            pthread_mutex_unlock(&lock);

            break;
        }
    }
    return nullptr;
}

int main(void)
{
    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, NULL, route, (void *)"thread 1");
    pthread_create(&t2, NULL, route, (void *)"thread 2");
    pthread_create(&t3, NULL, route, (void *)"thread 3");
    pthread_create(&t4, NULL, route, (void *)"thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}

初始化互斥量:

pthread_mutex=PTHREAD_NUTEX_INITIALIZER

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t* mutex);

int pthread_mutex_unlock(pthread_mutex_t* mutex);

相关推荐
阿部多瑞 ABU40 分钟前
`chenmo` —— 可编程元叙事引擎 V2.3+
linux·人工智能·python·ai写作
徐同保1 小时前
nginx转发,指向一个可以正常访问的网站
linux·服务器·nginx
HIT_Weston1 小时前
95、【Ubuntu】【Hugo】搭建私人博客:_default&partials
linux·运维·ubuntu
实心儿儿2 小时前
Linux —— 基础开发工具5
linux·运维·算法
oMcLin2 小时前
如何在SUSE Linux Enterprise Server 15 SP4上通过配置并优化ZFS存储池,提升文件存储与数据备份的效率?
java·linux·运维
王阿巴和王咕噜6 小时前
【WSL】安装并配置适用于Linux的Windows子系统(WSL)
linux·运维·windows
布史6 小时前
Tailscale虚拟私有网络指南
linux·网络
水天需0106 小时前
shift 命令详解
linux
wdfk_prog6 小时前
[Linux]学习笔记系列 -- 内核支持与数据
linux·笔记·学习
Xの哲學7 小时前
深入剖析Linux文件系统数据结构实现机制
linux·运维·网络·数据结构·算法