Linux之线程控制

目录

一、POSIX线程库

二、线程的创建

三、线程等待

四、线程终止

五、分离线程

六、线程ID:pthread_t

1、获取线程ID

2、pthread_t

七、线程局部存储:__thread


一、POSIX线程库

由于Linux下的线程并没有独立特有的结构,所以Linux并没有提供线程相关的接口。

而我们所说的,pthread线程库是应用层的原生线程库。这个线程库并不是系统接口直接提供的,而是由第三方帮我们提供的。

1、与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
2、要使用这些函数库,要通过引入头文<pthread.h>
3、链接这些线程函数库时要使用编译器命令的“-lpthread”选项

二、线程的创建

pthread_create:其功能就是创建线程。

NAME
       pthread_create - create a new thread

SYNOPSIS
       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

       Compile and link with -pthread.

参数说明:

thread:获取创建成功的线程ID,该参数是一个输出型参数。

attr:用于设置创建线程的属性,传入nullptr表示使用默认属性。(我们一般不关心,直接设为nullptr)

start_routine:该参数是一个函数指针,表示线程启动后要执行的函数。

arg:传给线程执行函数的参数。

返回值:线程创建成功返回0,失败返回错误码。返回值也可以自己设置,返回给主线程。主线程通过pthread_join获取。

主线程:当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,这个线程就叫做主线程。

下面我们让主线程调用pthread_create函数创建一个新线程:

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

using namespace std;

void *thread_run(void *argc)
{
    cout << "new thread pid: " << getpid() << "\n"
         << endl;

    sleep(20);
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread 1");

    while (true)
    {
        cout << "main thread pid: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

 

使用ps -aL命令,可以显示当前的轻量级进程。

从上图,我们看到两个线程的PID相同,说明他们属于同一个进程。但是他们的LWP值不同,说明他们是两个不同的线程。LWP就是轻量级进程的ID。

注:在Linux中,线程与内核的LWP是一一对应的,实际上操作系统调度的时候是根据LWP调度的,而不是PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的,所以对于单线程进程来说,调度时采用PID和LWP是一样的。

我们也可以让一个主线程创建多个新线程

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

using namespace std;

void *thread_run(void *argc)
{
    string name = (char *)argc;
    while (true)
    {
        cout << name << "---"
             << "pid: " << getpid() << "\n"
             << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid[5];
    char name[64];
    for (int i = 0; i < 5; i++)
    {
        snprintf(name, sizeof(name), "%s-%d", "thread", i);
        pthread_create(tid + i, nullptr, thread_run, (void *)name);
        sleep(1);
    }

    while (true)
    {
        cout << "main thread pid: " << getpid() << endl;
        sleep(3);
    }

    return 0;
}

因为主线程和五个新线程都属于同一个进程,所以它们的PID都是一样的。 

三、线程等待

一个线程被创建出来,那么这个线程就如同进程一般,也是需要被等待的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。如果不等待会产生类似于“僵尸进程”的问题,也就会造成内存泄漏。所以线程需要被等待。

pthread_join:其功能就是进行线程等待

NAME
       pthread_join - join with a terminated thread

SYNOPSIS
       #include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);

       Compile and link with -pthread.

参数说明:

thread:被等待线程的ID。
retval:线程退出时的退出码信息。

返回值:线程等待成功返回0,失败返回错误码。

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

using namespace std;

void *thread_run(void *argc)
{
    int count = 10;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    pthread_join(tid, nullptr);
    cout << "main thread wait done ... quit" << endl;

    return 0;
}

第二个参数是用来获取新线程返回值的。主线程可以通过新线程的返回值拿到新线程的计算结果(该结果也可以保存在堆空间上)

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

using namespace std;

void *thread_run(void *argc)
{
    int count = 10;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << "main thread wait done ... quit"
         << " " << (long long)ret << endl;

    return 0;
}

四、线程终止

return:最简单的终止线程的方式,就是使用return返回一个返回值来终止线程。

pthread_exit:其功能就是终止一个线程。(终止线程不能使用exit,因为它是用来终止进程的)

参数,retval:设置退出结果。

NAME
       pthread_exit - terminate calling thread

SYNOPSIS
       #include <pthread.h>

       void pthread_exit(void *retval);

       Compile and link with -pthread.
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>

using namespace std;

void *thread_run(void *argc)
{
    int count = 10;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
    pthread_exit((void*)17);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << "main thread wait done ... quit"
         << " " << (long long)ret << endl;

    return 0;
}

 

pthread_cancel:其功能是取消一个线程。

参数,thread:线程ID。

NAME
       pthread_cancel - send a cancellation request to a thread

SYNOPSIS
       #include <pthread.h>

       int pthread_cancel(pthread_t thread);

       Compile and link with -pthread.
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>

using namespace std;

void *thread_run(void *argc)
{
    string name = (char *)argc;
    int count = 10;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    void *ret = nullptr;
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    cout << "main thread wait done ... quit"
         << " " << (long long)ret << endl;

    return 0;
}

 

线程被取消,线程等待时获取的退出码为-1。 

五、分离线程

新线程退出后,主线程需要对其进行pthread_join操作,否则无法释放资源,从而造成内存泄漏。
但如果主线程不关心新线程的返回值,此时我们可以将该新线程进行分离,后续当新线程退出时就会自动释放线程资源。

一个线程如果被分离了,这个线程依旧要使用该进程的资源,依旧在该进程内运行,甚至这个线程崩溃了一定会影响其他线程,只不过这个线程退出时不再需要主线程去join了,当这个线程退出时系统会自动回收该线程所对应的资源。

pthread_detach:其功能就是进行分离线程。一般是线程自己分离。

int pthread_detach(pthread_t thread);

参数说明:thread:被分离线程的ID。

返回值说明:

线程分离成功返回0,失败返回错误码。

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

using namespace std;

void *thread_run(void *argc)
{
    pthread_detach(pthread_self());
    int count = 10;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
    pthread_exit((void*)17);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    void *ret = nullptr;
    cout << "main thread wait done ... quit"
         << " " << (long long)ret << endl;

    return 0;
}

 如果我们在线程分离了之后,任然等待,会怎么样呢?

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

using namespace std;

void *thread_run(void *argc)
{
    pthread_detach(pthread_self());
    int count = 9;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit" << endl;
    pthread_exit((void *)17);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    sleep(2);
    int n = pthread_join(tid, nullptr);
    cout << "n: " << n << "errstring: " << strerror(n) << endl;

    return 0;
}

六、线程ID:pthread_t

pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,该线程ID和内核中的LWP是完全不一样的。内核中的LWP属于进程调度的范畴,需要一个数值来唯一表示该线程。

那么pthread_t到底是什么类型呢?

1、获取线程ID

pthread_self:获取线程的ID。

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

using namespace std;

void *thread_run(void *argc)
{
    int count = 9;
    while (true)
    {
        sleep(1);
        if (count++ == 10)
            break;
    }

    cout << "new thread  done ... quit"
         << "new thread ID: " << pthread_self() << endl;
    pthread_exit((void *)17);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_run, (void *)"thread1");
    void *ret = nullptr;
    sleep(2);
    cout << "main thread ID: " << pthread_self() << endl;
    pthread_join(tid, &ret);

    return 0;
}

为什么线程的ID数值这么大呢?下面我们就来讲一讲。 

2、pthread_t

进程运行时线程动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时该进程内的所有线程都是能看到这个动态库的。

其中主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是由线程库帮我们在共享区中开辟的。

线程库给每个新线程提供属于自己的struct pthread,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。其中,还有线程栈。如下图:

所以,线程ID本质就是进程地址空间共享区上对应的struct pthread的虚拟地址。 

七、线程局部存储:__thread

假设有一个全局变量:g_val。我们知道,各个线程是共享全局变量的。不同的线程可以对同一个全局变量进行操作。那么如果我们想让每个线程都拥有属于自己的g_val,那么我们可以加上关键字:__thread。这种现象就叫做线程局部存储。

  • 75
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 49
    评论
### 回答1: Linux线程同步的方式有很多,以下是其中一些常见的: 1. 互斥锁(Mutex):用于保护共享资源,只允许一个线程访问共享资源。当一个线程获取到互斥锁后,其他线程就必须等待这个线程释放锁后才能获取锁。 2. 读写锁(Reader-Writer Lock):在读多写少的情况下,使用读写锁可以提高并发性能。读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。 3. 条件变量(Condition Variable):用于等待某个条件满足时才执行。当一个线程等待某个条件变量时,它会被阻塞,直到另外一个线程发出信号,通知条件已经满足,该线程才会继续执行。 4. 信号量(Semaphore):用于控制同时访问共享资源的线程数量。信号量可以是计数信号量或二进制信号量。计数信号量可以用来控制多个线程同时访问一个资源的数量,而二进制信号量只允许一个线程访问资源。 5. 屏障(Barrier):用于让多个线程在某个点上同步执行。当多个线程执行到某个点时,它们会被阻塞,直到所有线程都到达该点,才会继续执行。 这些同步机制可以根据具体的场景选择使用。在实际开发中,需要考虑多线程的安全性、性能等因素,选择合适的同步机制来实现线程同步。 ### 回答2: 在Linux中,线程同步是指多个线程之间的操作需要协调,以确保它们在执行任务时能够按照预期的顺序进行。 线程同步的目的是确保多个线程共享的资源(如共享内存、文件、网络连接等)能够被有序地访问和操作,避免出现竞态条件和资源争夺等问题,确保程序的正确性和性能。 常见的线程同步机制包括互斥锁、条件变量、读写锁、信号量等。 互斥锁是最基本的一种线程同步机制,它可以确保在任何时候只有一个线程可以访问共享资源。当某个线程获取了互斥锁之后,其他线程必须等待该线程释放锁后才能继续执行。互斥锁通过使用标志位和原子操作来确保线程的互斥性。 条件变量是一种线程同步机制,它可以使线程在满足某些条件之前一直等待,从而避免忙等待和浪费资源。条件变量常与互斥锁一起使用,当共享资源不满足条件时,线程可以使用条件变量进入等待状态,直到该条件被满足,另一个线程发出信号来唤醒等待线程。 读写锁是一种用于多线程读写共享资源的机制,它允许多个线程同时进行读操作,但只允许一个线程进行写操作。读写锁可以提高程序的并发性能,但需要注意避免读-写之间的竞争条件。 信号量是一种基于计数器的线程同步机制,它可以控制共享资源的访问数量和顺序。信号量可以实现互斥锁、条件变量等多种功能,是一种比较通用的线程同步机制。 除了上述机制,Linux中还有其他一些线程同步工具和算法,如屏障、自旋锁、分段锁、标记等。不同的线程同步机制和算法适用于不同的场景和需求,需要根据具体情况进行选择和使用。 ### 回答3: 在Linux中,由于多线程同时访问共享资源可能导致竞争条件的出现,因此需要使用线程同步技术来避免这种情况。除了使用互斥锁和条件变量来实现线程同步之外,也可以使用Linux提供的信号量机制。 信号量是一个整数值,用于控制对共享资源的访问。它包括两个主要的操作:PV操作和初始化操作。PV操作分为两种:P操作(等待操作)和V操作(释放操作)。一个线程在访问共享资源之前,必须执行P操作,如果信号量的值为0,则该线程将被阻塞。当线程使用完共享资源后,必须执行V操作来释放信号量,并唤醒其他等待访问共享资源的线程。 在Linux中使用信号量需要包含头文件<sys/sem.h>,并使用semget函数创建一个新的信号量集。接着,使用semctl函数可对信号量进行初始化或者删除操作。使用semop函数可进行PV操作。 与互斥锁和条件变量相比,信号量机制的优点是可以在不同进程间进行线程同步,而且可以实现多个线程同时访问共享资源的问题。但是,使用信号量需要特别小心,因为它比互斥锁和条件变量更难调试,如果使用不当会导致死锁等问题。 总之,Linux提供了多种线程同步机制,开发人员需要根据实际需求选择合适的机制来避免竞争条件的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值