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

发布于:2025-03-23 ⋅ 阅读:(25) ⋅ 点赞:(0)

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就是使用的地址。

#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回收,主线程就无权限访问了。

#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文件

#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文件

#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函数

#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函数

#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*` 类型。

3. **线程生命周期管理**:
   - 在 `Thread` 的构造函数中调用 `pthread_create` 创建线程。
   - 在 `Thread` 的析构函数中调用 `pthread_join` 等待线程结束,确保线程安全退出。

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

多线程创建执行任务

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

#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文件

#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文件

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

#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前缀,线程局部存储也有限制,只能存储内置类型和部分指针。

代码示例

#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

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

代码示例

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

// 操作共享变量会有问题的售票系统代码
#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);