【Linux】线程函数和线程同步详细整理(金针菇般细)

目录

一,线程函数

1.获取当前线程ID

2.创建线程

3.退出线程

4.阻塞线程

5.分离线程

6.取消线程

7.线程比较

8.测试代码(线程函数总结)

二,线程同步

1.互斥锁

互斥锁测试代码

2.读写锁

读写锁测试代码

3.条件变量

条件变量测试代码

4.信号量

信号量测试代码(总资源数等于1)

信号量测试代码(总资源数大于1)


一,线程函数

man command                //Linux终端命令

# 查阅 command 命令的使用手册,包含了绝大部分的命令和函数的详细使用说明

获取当前线程ID pthread_t pthread_self(void);
创建线程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),void *arg);
退出线程 void pthread_exit(void *retval);
阻塞线程 int pthread_join(pthread_t thread, void **retval);
分离线程 int pthread_detach(pthread_t thread);
取消线程(杀死线程) int pthread_cancel(pthread_t thread);
线程比较 int pthread_equal(pthread_t t1, pthread_t t2);

1.获取当前线程ID

//获取当前线程的线程ID,ID类型为pthread_t,它是一个无符号长整型数
pthread_t pthread_self(void);
  • 返回值: 永远返回成功,返回被调用者的线程ID

2.创建线程

//在进程中调用线程创建函数来创建一个子线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),void *arg);
参数介绍
thread 输出参数,线程创建成功会将线程ID写入到该指针所指向的内存中
attr 线程的属性,一般设置为NULL,即线程的创建使用默认属性
start_routine 函数指针,子线程的业务处理函数,即传入的函数指针会在该子线程中执行
arg 函数参数,和start_routine指针指向的函数参数匹对,即该参数是传递给start_routine指针所指向的函数
  • 返回值:创建成功返回0,创建失败返回一个错误号并且*thread值未定义

3.退出线程

//线程退出不会影响到其它线程的正常运行,子线程或主线程都可以调用
void pthread_exit(void *retval);
  • 参数:被调用线程退出时,其它线程可通过retval获取线程退出时携带的数据。不需要数据可以指定为NULL

4.阻塞线程

//函数被调用一次,只会回收一个子线程,如果有多个线程需要循环传入线程ID进行回收
int pthread_join(pthread_t thread, void **retval);
  • 参数介绍
    • thread:要被阻塞的线程ID,指定的线程必须是可被阻塞的
    • retval:所指向一级指针的地址是一个输出参数,该地址中存储了pthread_exit()传出的数据,不需要数据可以指定为NULL
  • 返回值:成功返回0,失败返回一个错误码

5.分离线程

/*
1.传入分离子线程的线程ID,便会与主线程分离,当子线程退出的时候,其占用的资源就会被系统的其它进程接管并回收。
2.不可对同一线程重复分离,不然会出现未定义行为
*/
int pthread_detach(pthread_t thread);

6.取消线程

int pthread_cancel(pthread_t thread);

7.线程比较

int pthread_equal(pthread_t t1, pthread_t t2);

8.测试代码(线程函数总结)

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//子线程处理代码
void* threadFun(void *_arg){
    printf("子线程线程ID:%ld 传入参数值:%ld\n", pthread_self(),*(int*)_arg);
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("subThread-i: = %d\n", i);
        if (i == 3) {
            int* val = (int*)malloc(sizeof(int));
            *val = i;
            pthread_exit(val);  //子线程退出携带的数据val可以被主线程中调用pthread_join获取
        }
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());
    for (int i = 0; i < 5; i++)
    {
        sleep(1);
        printf("main-i: = %d\n", i);
    }

    pthread_t tid;  //创建一个子线程
    int thread_arg = 1008;   //传给threadFun()线程工作函数的参数
    int flag = pthread_create(&tid, nullptr, threadFun, (void*)&thread_arg);
    if (flag == 0)
    {
        printf("子线程创建成功,线程ID:%ld  传入threadFun()线程函数的参数值:%d\n", tid,thread_arg);
    }
   
    //pthread_detach(tid);    
    /*
    * 线程分离之后在主线程使用pthread_join()就会报段错误(核心已转储)
    * ,因为子线程退出时,其占用的内核资源被系统其它进程回收了,然而你又对它进行操作。
    */
    void *subTd_retval = nullptr;

    pthread_join(tid, &subTd_retval);   //阻塞子线程,并获取子线程退出时的数据
    printf("子线程 %ld 返回的数据:%ld\n", tid, *(int*)subTd_retval);
    
    pthread_detach(tid);    //这里让子线程与主线程分离
    pthread_exit(nullptr);  //让主线程自己退出
    return 0;
}

二,线程同步

 参考文章: 线程同步 | 爱编程的大丙

1.当多个线程对共享资源(多个线程共同访问的内存区)进行访问的时候就会出现数据混乱的问题,所以就需要进行线程同步,所谓的线程同步实际上是各线程按先后顺序依次对共享资源进行访问,而不是同时进行的,其实也就是让各线程去抢占CPU时间片,抢到就访问数据,没抢到就挂起,这之间会涉及到上下文的切换。虽然降低了运行效率,但提高了数据访问的安全性,这是值得的。

2.线程同步有四种方式:互斥锁,读写时,条件变量,信号量。

3.互斥锁,读写时,条件变量在头文件pthread中,信号量在头文件semaphore中。

1.互斥锁

//每一个共享资源对应一把锁,锁的个数和线程的个数无关
pthread_mutex_t mutex;

//初始化互斥锁,被restrict修饰的指针可以访问指向的内存地址,attr一般默认为 NULL 就行
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

//释放互斥锁资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);

//修改互斥锁状态,对传入的互斥锁进行加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

//对传入的互斥锁进行尝试加锁,
int pthread_mutex_trylock(pthread_mutex_t *mutex);

//对传入的互斥锁进行解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

互斥锁测试代码

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex;

int g_number = 0;       //多线程访问的共享资源,如果不对该共享资源加锁会造成数据的混乱

void* thread_fun1(void* _arg)
{
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("1号子线程ID = %ld,对共享资源g_number进行加一操作:g_number = %d\n", pthread_self(), g_number);
        pthread_mutex_unlock(&mutex);       对访问的临界区解锁
        sleep(rand() % 3);
    }
    return nullptr;
}

void* thread_fun2(void* _arg)
{
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("2号子线程ID = %ld,对共享资源g_number进行加一操作:g_number = %d\n", pthread_self(), g_number);
        pthread_mutex_unlock(&mutex);     //对访问的临界区加锁
        sleep(rand() % 3);
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());

    pthread_mutex_init(&mutex, nullptr);    //初始化互斥锁

    pthread_t p1, p2;

    //创建两个子线程
    int pth1 = pthread_create(&p1, nullptr, thread_fun1, nullptr);
    int pth2 = pthread_create(&p2, nullptr, thread_fun2, nullptr);
    if (pth1 == 0) {
        printf("1号子线程创建成功,线程ID:%ld \n", p1);
    }
    if (pth2 == 0) {
        printf("2号子线程创建成功,线程ID:%ld \n", p2);
    }

    //回收互斥锁占用的资源
    pthread_mutex_destroy(&mutex);

    //阻塞回收子线程资源
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    return 0;
}

2.读写锁

//读写锁是互斥锁的升级版,读锁是共享的,写锁是互斥(独占)的。写锁优先级比读锁高
pthread_rwlock_t rwlock;

//初始化读写锁,attr一般默认为 NULL 就行
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

//释放读写锁占用的系统资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

//修改读写锁状态,锁定读操作,读锁可以重复锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

//对传入的读写锁进行尝试加读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

//对传入的读写锁进行加写锁
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);

//对传入的读写锁进行尝试加写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

//对传入的读写锁进行解锁(不分读锁写锁都可解锁)
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

读写锁测试代码

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_rwlock_t rwlock;    //读写锁:写锁优先级比读锁高,写锁独占资源,读锁共享资源

int g_number = 0;       //多线程访问的共享资源

void* thread_write(void* _arg)
{
    for (int i = 0; i < 10; i++) {
        pthread_rwlock_wrlock(&rwlock);     //对访问的临界区加写锁
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("子线程ID = %ld,写操作:g_number = %d\n", pthread_self(), g_number);
        pthread_rwlock_unlock(&rwlock);       //对访问的临界区解锁
        sleep(rand()%3);
    }
    return nullptr;
}

void* thread_read(void *_arg)
{
    for (int i = 0; i < 10; i++) {
        pthread_rwlock_rdlock(&rwlock);     //对访问的临界区加写锁
        printf("子线程ID = %ld,读操作:g_number = %d\n", pthread_self(), g_number);
        pthread_rwlock_unlock(&rwlock);     //对访问的临界区解锁
        sleep(rand() % 3);
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());

    pthread_rwlock_init(&rwlock, nullptr);    //初始化读写锁

    pthread_t wrTid[3];     //三个写线程
    pthread_t rdTid[5];     //三个读线程

    for (int i = 0; i < 3; i++) {
        int pth1 = pthread_create(&wrTid[i], nullptr, thread_write, nullptr);
    }
    for (int i = 0; i < 5; i++) {
        int pth1 = pthread_create(&rdTid[i], nullptr, thread_read, nullptr);
    }

    //回收读写锁占用的资源
    pthread_rwlock_destroy(&rwlock);

    //阻塞回收子线程资源
    for (int i = 0; i < 3; i++) {
        pthread_join(wrTid[i], nullptr);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(rdTid[i], nullptr);
    }
    return 0;
}

​​​​​​​

3.条件变量

  • 条件变量不是处理线程同步的,而是进行线程的阻塞。多线程实现线程同步需要配合互斥锁来使用。
//条件变量类型的定义(定义全局的,各线程都有作用域,可在条件变量上进行线程阻塞和解除阻塞)
pthread_cond_t cond;

//初始化条件变量,attr一般默认为 NULL 就行
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_cond_t *restrict attr);

//释放条件变量资源
int pthread_cond_destroy(pthread_cond_t *cond);

//线程阻塞函数,哪个线程调用这个函数就会阻塞哪个线程
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

//唤醒在条件变量上阻塞的线程(至少一个)
int pthread_cond_signal(pthread_cond_t *cond);

//唤醒在条件变量阻塞的全部线程
int pthread_cond_broadcast(pthread_cond_t *cond);

条件变量测试代码

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

//生产者生产一个产品进行加一,消费者消耗一个产品就减一
int g_number = 0;       //多线程访问的共享资源,如果不对该共享资源加锁会造成数据的混乱

void* thread_producer(void* _arg)
{
    while (true) {  //一直生产,无上限
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("生产者子线程ID = %ld -> 生产进行数量+1 剩余产品:g_number = %d\n", pthread_self(), g_number);
        pthread_mutex_unlock(&mutex);       对访问的临界区解锁

        //生产了产品,通知所有消费者去竞争该产品
        pthread_cond_broadcast(&cond);
        sleep(rand()%3);    //慢点生产
    }
    return nullptr;
}

void* thread_consumer(void* _arg)
{
    while (true) {  //一直消费,有下限,当产品 g_number 为0时就阻塞所有消费者
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        while (g_number == 0) {
            /*
            * 可以形象比喻,一开始消费者就非常着急要产品,一直催催催,但这时候所有生产者都还没生产产品(产品数量为0)
            * ,所以消费者一直在催的时候就要一直去判断生产者是否生产了产品,生产者没有生产就让消费者一直阻塞,直到
            * 生产者生产了产品,就通知所有消费者去抢有限数量的产品,当产品又被消费为0时就又阻塞当前访问的消费者线程,循环往复!
            */

            pthread_cond_wait(&cond, &mutex);
        }
        int curNum = g_number;
        curNum--;
        g_number = curNum;
        printf("消费者子线程ID = %ld -> 消费进行数量-1 剩余产品:g_number = %d\n", pthread_self(), g_number);
        pthread_mutex_unlock(&mutex);     //对访问的临界区加锁
        sleep(rand() % 3);  //慢点消费
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());

    pthread_mutex_init(&mutex, nullptr);    //初始化互斥锁

    pthread_t p1[3], p2[3];

    //创建3个生产者子线程,3个消费者子线程
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p1[i], nullptr, thread_producer, nullptr);
        if (pth == 0) {
            printf("生产者子线程创建成功,线程ID:%ld \n", p1[i]);
        }
    }
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p2[i], nullptr, thread_consumer, nullptr);
        if (pth == 0) {
            printf("消费者子线程创建成功,线程ID:%ld \n", p2[i]);
        }
    }

    //回收条件变量占用的资源
    pthread_cond_destroy(&cond);

    //回收互斥锁占用的资源
    pthread_mutex_destroy(&mutex);

    //阻塞回收子线程生产者/消费者资源
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p1[i], nullptr);
    }
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p2[i], nullptr);
    }
    return 0;
}

4.信号量

  •  信号量 主要用于多线程环境下保护某个临界资源不被并发访问,通常配合互斥锁一起使用来保证线程的安全
  • 信号量对应头文件为:<semaphore.h>

1.定义信号量变量

sem_t sem;

2.初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 参数
    • sem:信号量变量地址
    • pshared:0表示线程同步,!0表示进程同步
    • value:初始化信号量*sem拥有的资源数(>=0),为0就会阻塞线程

3.销毁信号量

int sem_destroy(sem_t *sem);

4.锁住一个信号量资源进行线程占用(不会阻塞),当资源数=0时会阻塞访问的线程

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

5.释放一个信号量资源

//会解除因为信号量sem资源为0时而阻塞的线程
int sem_post(sem_t *sem);

6.获取信号量的资源数

//sval是输出参数,可以通过sval获取信号量sem中拥有的资源数量
int sem_getvalue(sem_t *sem, int *sval);

信号量测试代码(总资源数等于1)

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

sem_t psem;     //生产者信号量
sem_t csem;     //消费者信号量

//生产者生产一个产品进行加一,消费者消耗一个产品就减一
int g_number = 0;       //多线程访问的共享资源,如果不对该共享资源加锁会造成数据的混乱

void* thread_producer(void* _arg)
{
    while (true) {  //一直生产,无上限
        sem_wait(&psem);    //生产者拿到一个信号量资源,进行资源生产(psem资源数减一)
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("生产者子线程ID = %ld -> 生产进行数量+1 总共产品:g_number = %d\n", pthread_self(), g_number);

        sem_post(&csem);    //生产了产品,给消费者信号量资源加一,通知所有消费者去竞争该产品(csem资源数加一)
        sleep(rand()%3);    //慢点生产
    }
    return nullptr;
}

void* thread_consumer(void* _arg)
{
    while (true) {  //一直消费,有下限,当产品 g_number 为0时就阻塞所有消费者
        sem_wait(&csem);    //消费者拿到一个信号量资源,进行消费(csem资源数减一)
        int curNum = g_number;
        curNum--;
        g_number = curNum;
        printf("消费者子线程ID = %ld -> 消费进行数量-1 剩余产品:g_number = %d\n", pthread_self(), g_number);
        
        sem_post(&psem);    //通知生产者生产产品,给生产者信号量资源加一(psem资源数加一)
        sleep(rand() % 3);  //慢点消费
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());

    //初始化信号量
    sem_init(&psem, 0, 1);  //生产者线程的信号量总共只有一个资源
    sem_init(&csem, 0, 0);  //消费者线程的信号量总共只有0个资源,所以一开始就会阻塞消费者线程

    pthread_t p1[3], p2[3];

    //创建3个生产者子线程,3个消费者子线程
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p1[i], nullptr, thread_producer, nullptr);
        if (pth == 0) {
            printf("生产者子线程创建成功,线程ID:%ld \n", p1[i]);
        }
    }
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p2[i], nullptr, thread_consumer, nullptr);
        if (pth == 0) {
            printf("消费者子线程创建成功,线程ID:%ld \n", p2[i]);
        }
    }

    //回收信号量占用的资源
    sem_destroy(&psem);
    sem_destroy(&csem);

    //阻塞回收子线程资源
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p1[i], nullptr);
    }
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p2[i], nullptr);
    }
    return 0;
}

  • 总结:从运行效果看到,如果生产者和消费者使用的信号量总共资源数为1,那么所有的生产者线程和消费者线程对共享临界资源都是线性访问的,那么这时候就不需要对共享资源进行原子操作了(如果总资源数大于就会存在多线程竞争共享资源的问题)

信号量测试代码(总资源数大于1)

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

sem_t psem;     //生产者信号量
sem_t csem;     //消费者信号量

pthread_mutex_t mutex;  //全局互斥锁

//生产者生产一个产品进行加一,消费者消耗一个产品就减一
int g_number = 0;       //多线程访问的共享资源,如果不对该共享资源加锁会造成数据的混乱

void* thread_producer(void* _arg)
{
    while (true) {  //一直生产,无上限
        sem_wait(&psem);    //生产者拿到一个信号量资源,进行资源生产(psem资源数减一)
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        int curNum = g_number;
        curNum++;
        g_number = curNum;
        printf("生产者子线程ID = %ld -> 生产进行数量+1 总共产品:g_number = %d\n", pthread_self(), g_number);

        pthread_mutex_unlock(&mutex);   //对访问的临界区解锁
        sem_post(&csem);    //生产了产品,给消费者信号量资源加一,通知所有消费者去竞争该产品(csem资源数加一)
        sleep(rand()%3);    //慢点生产
    }
    return nullptr;
}

void* thread_consumer(void* _arg)
{
    while (true) {  //一直消费,有下限,当产品 g_number 为0时就阻塞所有消费者
        //pthread_mutex_lock(&mutex);
        /*
        * 不能将互斥锁加到sem_wait/sem_post外面,解释:如果第一个消费者抢到了这把互斥锁,进行共享资源的独自访问
        * 并把其它消费者都阻塞在这里,但消费者的信号量一开始的总资源数为0,虽然抢到了这把互斥锁,但因为信号量的
        * 资源数为0而又阻塞在sem_wait处,然后所有的消费者就都阻塞了,这时生产者因为互斥锁被消费者进行了加锁操作
        * ,所以所有了生产者线程也都会被阻塞了!所以最好将互斥锁加到sem_wait/sem_post里面
        */
        sem_wait(&csem);    //消费者拿到一个信号量资源,进行消费(csem资源数减一)
        pthread_mutex_lock(&mutex);     //对访问的临界区加锁
        int curNum = g_number;
        curNum--;
        g_number = curNum;
        printf("消费者子线程ID = %ld -> 消费进行数量-1 剩余产品:g_number = %d\n", pthread_self(), g_number);
        
        pthread_mutex_unlock(&mutex);   //对访问的临界区解锁
        sem_post(&psem);    //通知生产者生产产品,给生产者信号量资源加一(psem资源数加一)
        sleep(rand() % 3);  //慢点消费
    }
    return nullptr;
}

int main()
{
    printf("主线程线程ID:%ld\n", pthread_self());

    //初始化信号量
    sem_init(&psem, 0, 5);  //生产者线程的信号量总共只有一个资源
    sem_init(&csem, 0, 0);  //消费者线程的信号量总共只有0个资源,所以一开始就会阻塞消费者线程

    //初始化互斥锁
    pthread_mutex_init(&mutex, nullptr);

    pthread_t p1[3], p2[3];

    //创建3个生产者子线程,3个消费者子线程
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p1[i], nullptr, thread_producer, nullptr);
        if (pth == 0) {
            printf("生产者子线程创建成功,线程ID:%ld \n", p1[i]);
        }
    }
    for (int i = 0; i < 3; i++)
    {
        int pth = pthread_create(&p2[i], nullptr, thread_consumer, nullptr);
        if (pth == 0) {
            printf("消费者子线程创建成功,线程ID:%ld \n", p2[i]);
        }
    }

    //回收信号量占用的资源
    sem_destroy(&psem);
    sem_destroy(&csem);

    //阻塞回收子线程资源
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p1[i], nullptr);
    }
    for (int i = 0; i < 3; i++)
    {
        pthread_join(p2[i], nullptr);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43729127/article/details/129111194