Linux:线程概念 | 线程操作 | 原生线程库初识

文章目录

          • 1.什么是线程
          • 2.进程和线程的比较
          • 3.线程创建
            • 3.1.创建线程:pthread_create
            • 3.2.线程终止:pthread_exit
            • 3.3.线程取消:pthread_cancel
            • 3.4.线程等待:pthread_join
            • 3.5.线程分离:pthread_detach

1.什么是线程

线程是进程内的一个执行分支;线程的执行力度比进程要细。

同一个进程内的多个线程大多数的资源都是共享的,如:代码段、数据段、文件描述符表、每种信号的处理方式、用户id和组id等资源;但是但每个线程各自都有一套独立的寄存器和栈,独立的一组寄存器确保了线程有独立的上下文,能够体现线程是别独立调度的;独立的栈结构确保了运行时执行流不会发生错乱。

线程的优缺点

优点:

相对于进程而言,创建一个新线程的代价要比创建一个新进程小得多;线程之间的切换需要操作系统做的工作要少很多;线程占用的资源要比进程少很多;线程之间(共用一个地址空间)天生就具备共享资源的能力。

I/O密集型应用,为了提高性能,将I/O操作重叠;合理的使用多线程,能提高IO密集型程序的用户体验。(重要用途,如:迅雷的边下边播;平常一边写代码,一边下载库这些都是多线程的用途)

缺点:

健壮性降低:在C、C++程序中当进程中的一个线程崩溃时,会导致其所属进程的所有线程崩溃,也就是说整个进程就会崩溃。(Java程序不会崩)

缺乏访问控制:在一个线程中调用某些OS函数会对整个进程造成影响。

编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。

2.进程和线程的比较
  • **进程是分配系统资源的基本实体,线程(进程内部执行流资源)是 CPU 调度的单位;**进程通过地址空间(进程的资源窗口) + 页表的方式来获取物理资源。
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的寄存器和栈;
  • 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;(注意在Linux操作系统当中,使用进程的数据结构和算法来管理线程,进程相关的基本状态,线程也有)
  • 线程能减少并发执行的时间和空间开销;

为什么说线程比进程更加轻量化?

这是一个进程,创建一个进程要创建对应的PCB,对应的页表等一系列工作,创建线程只需要创建对应的PCB,释放同理,所以线程的创建和释放都比进程要更轻轻量化。

根据局部性原理,进程的切换需要的重新加载cache数据,而线程则不需要。

3.线程创建

Linux中没有"真正意义上的线程",它只有**轻量级进程(LWP)**的概念,它只会提供轻量级进程的系统调用接口,但是对于用户而言更多关注的数线程的概念,所以我们会使用与原生线程库pthread来对线程操作,现在几乎所有的Linux操作系统都是自带pthread库来操作线程,pthread库集成轻量级系统调用的接口。

Windows的设计是直接将线程的设计在内核当中,而Linux而是采用更加卓越的复用进程的相关数据结构和算法来提供轻量级进程的概念;所以我们使用原生线程库实现的是用户级线程

Linux中线程使用进程PCB描述实现,并且同一个进程中的所有PCB共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化。使用原生线程库pthread_create创建线程会调用clone轻量级进程接口。pthread_create创建的用户级线程都在共享区进行管理。

接下来使用phtread库创建线程:

  • 要使用这些函数库,要通过引入头文
  • 由于我们使用的不是系统调用,链接这些线程函数库时要使用编译器命令的“-lpthread”选项

说明:下面的代码均为主逻辑代码,查看完整代码并想让程序跑起来请查看gitee项目链接

3.1.创建线程:pthread_create
原型:int pthread_create
(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数说明:
	thread:输出型参数,返回线程ID
	attr:设置线程的属性,attr为NULL表示使用默认属性
	start_routine:是个函数地址,线程启动后要执行的函数
	arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
 
pthread_t pthread_self(void);
功能:获取线程自身id
3.2.线程终止:pthread_exit
原型:void pthread_exit(void *retval)
功能:线程终止
参数:
    retval:retval注意不要指向一个局部变量。

普通创建的线程可以使用return终止进程,但是main线程return相当于exit函数,会终止整个进程

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

3.3.线程取消:pthread_cancel
原型:int pthread_cancel(pthread_t thread)
功能:取消一个执行中的线程
参数:
    thread:线程id
返回值:成功返回0;失败返回错误码

代码示例1:

int ret_code = 0; 

void* newThread(void*)
{
    int count = 0;
    while(true)
    {
        cout << "new thread run ; pid:" << getpid() << endl;
        sleep(1);

        ret_code = count;
        if(++count == 15)
        {
            pthread_exit(reinterpret_cast<void*>(ret_code));
        }
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,newThread,nullptr);

    int count = 0;
    while(true)
    {
        cout << "main thread run , pid:" << getpid() << endl;
        sleep(1);
        if(++count == 6)
        {
            pthread_cancel(tid);
            break;
        }       
    }
    cout << "new thread exit code number is " << ret_code << endl;

    return 0;
}

监控脚本

while :; do ps -aL | head -1 && ps -aL | grep mythead; sleep 1;done

部分输出结果:

main thread run , pid:2853
new thread run ; pid:2853
new thread exit code number is 4

两个执行流,对应的执行流都有相同的pid说明,两个执行流(线程)是在同一个进程中。控制修改count值来控制线程终止还是线程取消。

3.4.线程等待:pthread_join
功能:等待线程结束
原型
	int pthread_join(pthread_t thread, void **value_ptr);
参数
	thread:线程id
	value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

为什么要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。比如:main线程创建一个新线程但是main线程先退出就会造成内存泄漏。
    调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的.
//代码实例2:
class Response
{
public:
    Response(int result, int exitCode)
        : _result(result), _exitCode(exitCode)
    {}

public:
    int _result;
    int _exitCode;
};

class Request
{
public:
    Request(int start, int end, string threadName)
        : _start(start), _end(end), _threadName(threadName)
    {}
public:
    int _start;
    int _end;
    string _threadName;
};

struct Cal
{
    //没有static的情况‘void* (Request::*)(void*)’ to ‘void* (*)(void*)’
    static void *sum(void *args)
    {
        Request *req = static_cast<Request *>(args);
        Response *resp = new Response(0, 0);

        for (int i = req->_start; i <= req->_end; i++)
        {
            resp->_result += i;
        }
        delete req;
        return resp;
    }
};

int main()
{
    pthread_t tid;
    Request *req = new Request(1, 100, "sumThread");

    //注意这里的sum传参
    pthread_create(&tid, nullptr, &Cal::sum, req);

    int count = 0;

    while(1)
    {
        cout << "main thread " << endl;
        sleep(1);
        if(count++ == 10) 
            break;
    }
    void *ret;
    pthread_join(tid, &ret);
    Response *resp = static_cast<Response *>(ret);

    cout << "ret is " << resp->_result << ",the exitCode is " << 
        resp->_exitCode << endl;

    return 0;
}
3.5.线程分离:pthread_detach
  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
返回值:正确返回0,错误返回错误码
//代码示例4
void* sayHi(void* args)
{
    //方式2
    // pthread_detach(pthread_self());
    while(true)
    {
        cout << "the thread say hi now" << endl;
        sleep(1);
        if(++count == 5)
            break;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,sayHi,nullptr);

    // pthread_join(tid,nullptr);//阻塞式等待线程退出,如果不想等呢?线程分离!
    // 方式1
    pthread_detach(tid);
    cout << "the main thread ready exit" << endl;
    
    //joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
    // int ret = 0;
    // ret = pthread_join(tid,nullptr); 

    //保证main线程最后退,不然整个进程就会退出
    sleep(7);

    return 0;
}

你可能感兴趣的:(操作系统:Linux,linux,c++,c语言)