线程


线程(thread)

概念

是操作系统能够进行运算的调度的最小单位。
它被包括在进程中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并发执行不同的任务。
  • 在Linux系统上线程函数位于libpthread共享库中,在编译时要加上-pthread选项。

函数

       #include <pthread.h>

//创建线程
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

       #include <pthread.h>

//获取线程id
       pthread_t pthread_self(void);

       Compile and link with -pthread.

           
       #include <pthread.h>
//发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
//pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,
//直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置.
       int pthread_cancel(pthread_t thread);


       #include <pthread.h>
//线程的终止可以是调用了pthread_exit或者该线程的例程结束。也就是说,一个线程可以隐式的退出,也可以显式的调用pthread_exit函数来退出。
       void pthread_exit(void *retval);



       #include <pthread.h>
//等待线程结束,thread表示线程id,retval表示线程传出线程死亡原因
       int pthread_join(pthread_t thread, void **retval);
//retval = NULL表示不想知道线程死亡原因



       #include <pthread.h>

//让线程自行了断,主线程不管
       int pthread_detach(pthread_t thread);
代码
/*************************************************************************
        > File Name: exit.c
        > Author:
        > Mail:
        > Created Time: Mon 31 Jan 2022 01:07:16 PM CST
 ************************************************************************/

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




void* thrf1(void* arg) {
    printf("thread 1 returning\n");
    return (void*)1;
}

void *thrf2(void *arg) {
    printf("thread 2 exiting\n");
    pthread_exit((void*)2);
    return NULL;
}

void *thrf3(void *arg) {
    while (1) {
        printf("thread 3 sleep 1s\n");
        sleep(1);
    }
    return NULL;
}




int main() {
    pthread_t tid; //线程id
    void *status; //接收线程死亡原因

    pthread_create(&tid, NULL, &thrf1, NULL);
    pthread_join(tid, &status);
    printf("thread 1 exit code %ld\n", (long)status);

    pthread_create(&tid, NULL, &thrf2, NULL);
    pthread_join(tid, &status);
    printf("thread 2 exit code %ld\n", (long)status);

    pthread_create(&tid, NULL, thrf3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &status);
    printf("thread 3 exit code %ld\n", (long)status);


    return 0;
}

线程共享的资源与环境

  • 由于同一个进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的。
  • 如果定义一个函数,在各线程中都可以调用。
  • 如果定义一个全局变量,在各线程中都可以访问得到。
  • 文件描述符表
  • 各种信号的处理方式
  • 当前工作目录
  • 用户id和组id

各自一份

  • 线程id
  • 上下文,包括各种寄存器的值、程序计数器和栈指针
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

线程间同步

mutex

       #include <pthread.h>

       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; //互斥变量初始化

       pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

       pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

       int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

       int pthread_mutex_lock(pthread_mutex_t *mutex);

       int pthread_mutex_trylock(pthread_mutex_t *mutex);

       int pthread_mutex_unlock(pthread_mutex_t *mutex);

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_lock
  • 给资源上锁
  • 如果资源已经被别的线程加锁,则线程挂起等待。
pthread_mutex_trylock
  • 尝试给资源上锁
  • 如果一个线程既想得到资源,又不想等待,可用调用pthread_mutex_trylock,如果资源已经被别的线程加锁,这个函数会失败返回EBUSY,而不会是线程挂起等待。
pthread_mutex_unlock
  • 给资源解锁
pthread_mutex_destroy
  • 销毁锁
/*************************************************************************
        > File Name: addnum.c
        > Author:
        > Mail:
        > Created Time: Mon 31 Jan 2022 03:17:20 PM CST
 ************************************************************************/

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

int cnt = 0;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
void *cntadd(void *arg) {
    int val;


    for (int i = 1; i <= 100; i++) {
        //pthread_mutex_trylock(&mtx);
        pthread_mutex_lock(&mtx);
        val = cnt;
        printf("%p: %d\n", (void*)pthread_self(), val);
        cnt = val + 1;
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}




int main() {

    pthread_t td1, td2, td3;
    pthread_create(&td1, NULL, &cntadd, NULL);
    pthread_create(&td2, NULL, &cntadd, NULL);
    pthread_create(&td3, NULL, &cntadd, NULL);


    //不想得到线程死亡信息,等待线程结束
    pthread_join(td1, NULL);
    pthread_join(td2, NULL);
    pthread_join(td3, NULL);

    return 0;
}

条件变量

线程A需要等待某个条件成立才能继续往下执行,现在这个条件不成立,线程A阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A执行。

       #include <pthread.h>

       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

       int pthread_cond_signal(pthread_cond_t *cond);

       int pthread_cond_broadcast(pthread_cond_t *cond);

       int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

       int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const  struct  timespec *abstime);
       
           struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };


       int pthread_cond_destroy(pthread_cond_t *cond);
静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 定义环境变量cond,并初始化。

pthread_cond_init()
  • 初始化一个条件变量
  • cond_attr表条件变量属性,通常为默认值,传NULL即可
pthread_cond_wait()
  • 阻塞等待条件变量cond,并释放已掌握的互斥锁mutex(解锁互斥量)。
  • 以上操作为原子操作。
  • 当被唤醒时,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex)。
pthread_cond_timedwait()
  • 限时等待一个条件变量

  • time_t cur = time(NULL); 获取当前时间。
    
    struct timespec t; 定义timespec 结构体变量t
    
    t.tv_sec = cur+1; 限时1pthread_cond_timedwait (&cond, &mutex, &t);
    
pthread_cond_signal()
  • 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast()
  • 唤醒全部阻塞在条件变量上的线程
pthread_cond_destroy()
  • 销毁一个条件变量
返回值
  • 以上函数,成功返回0, 失败直接返回错误号。
生产者消费者模型代码演示
/*************************************************************************
        > File Name: cond.c
        > Author:
        > Mail:
        > Created Time: Mon 31 Jan 2022 07:52:31 PM CST
 ************************************************************************/

#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>


typedef struct Goods{
    int data;
    struct Goods *next;
} Goods;

Goods *head = NULL;
pthread_mutex_t headMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t hasGoods = PTHREAD_COND_INITIALIZER;

void *producer(void *arg) {
    Goods *newGoods;

    while (1) {

        newGoods = (Goods*)malloc(sizeof(Goods));
        newGoods->data = rand() % 101;

        //加锁
        pthread_mutex_lock(&headMutex);
        //头插法
        newGoods->next = head;
        head = newGoods;
        //解锁
        //pthread_mutex_unlock(&headMutex);

        //触发环境变量
        //pthread_cond_signal(&hasGoods);
        pthread_cond_broadcast(&hasGoods);
        //解锁
        pthread_mutex_unlock(&headMutex);
        //为啥先触发环境变量再解锁,因为先解锁的话,如果刚刚解锁就有一个不是阻塞环境变量线程的获取锁。
        printf("producer goods data: %d\n\n", newGoods->data);
        sleep(rand() % 2);
    }
    return NULL;
}

void *consumer(void *arg) {
    Goods *oldHead = NULL;
    while (1) {
        //加锁
        pthread_mutex_lock(&headMutex);

        while (!head) { //没有商品
            //等待事件触发,释放锁
            pthread_cond_wait(&hasGoods, &headMutex);
        }

        //有商品
        oldHead = head;
        head = head->next;
        //解锁
        pthread_mutex_unlock(&headMutex);

        printf("thread id:%ld, consumer goods data: %d\n\n", pthread_self(), oldHead->data);

        //释放商品内存
        free(oldHead);

        sleep(rand() % 3);
    }
    return NULL;
}


int main() {
    srand(time(NULL));

    pthread_t td1, td2, td3, td4, td5;
    pthread_create(&td1, NULL, &producer, NULL);
    pthread_create(&td2, NULL, &consumer, NULL);
    pthread_create(&td3, NULL, &consumer, NULL);
    pthread_create(&td5, NULL, &consumer, NULL);
    pthread_create(&td4, NULL, &consumer, NULL);



    pthread_join(td1, NULL);
    pthread_join(td2, NULL);
    pthread_join(td3, NULL);
    pthread_join(td4, NULL);
    pthread_join(td5, NULL);

    return 0;
}

信号量

  • 信号量和mutex类似,表示可用资源数量,和mutex不同的是这个数量可以大于1.这种信号量不仅可用于同一进程的线程同步,也可以用于不同进程间同步。

  • 这组函数是POSIX标准的无名信号量函数

  • semaphore变量的类型为sem_t.

       #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       int sem_wait(sem_t *sem);

       int sem_trywait(sem_t *sem);

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

       int sem_destroy(sem_t *sem);

       int sem_getvalue(sem_t *sem); 

sem_init
  • 第一个参数:
  • pshared:
    • 0表示信号量用于同一进程的线程同步。
  • value:表示可用资源数量
sem_wait
  • 调用sem_wait可用获得资源,semaphore的值减一。如果调用sem_wait()时,semaphore 已经是 0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait()。
sem_post
  • 释放资源,semaphore 加一,同时唤起挂起等待的线程。
/*************************************************************************
        > File Name: sem.c
        > Author:
        > Mail:
        > Created Time: Mon 31 Jan 2022 10:24:14 PM CST
 ************************************************************************/

#include<stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <stdlib.h>


#define Q_NUM 5
//创建5个信号的量

int q[Q_NUM]; //循环队列
int head, tail;

sem_t blankNumber, goodsNumber;


void *producer(void *arg) {

    while (1) {

        sem_wait(&blankNumber);
        q[tail] = rand() % 100 + 1;
        printf("producer goods %d\n", q[tail]);
        tail = (tail + 1) % Q_NUM;
        sem_post(&goodsNumber);

        sleep(rand() % 2);
    }

    return NULL;
}


void *consumer(void *arg) {

    while (1) {
        sem_wait(&goodsNumber);
        printf("consumer goods %d\n", q[head]);
        q[head] = 0;
        head = (head + 1) % Q_NUM;
        sem_post(&blankNumber);
        sleep(rand() % 3);
    }

    return NULL;
}





int main() {

    srand(time(NULL));

    sem_init(&blankNumber, 0, Q_NUM); // 空位数量
    sem_init(&goodsNumber, 0, 0); // 商品数量





    pthread_t pid1, pid2, pid3, cid1, cid2, cid3, cid4, cid5;


    pthread_create(&pid1, NULL, &producer, NULL);
    pthread_create(&cid1, NULL, &consumer, NULL);
    pthread_create(&pid2, NULL, &producer, NULL);
    pthread_create(&cid2, NULL, &consumer, NULL);
    pthread_create(&pid3, NULL, &producer, NULL);
    pthread_create(&cid3, NULL, &consumer, NULL);
    pthread_create(&cid4, NULL, &consumer, NULL);
    pthread_create(&cid5, NULL, &consumer, NULL);

    pthread_join(pid1, NULL);
    pthread_join(cid1, NULL);
    pthread_join(pid2, NULL);
    pthread_join(cid2, NULL);
    pthread_join(pid3, NULL);
    pthread_join(cid3, NULL);
    pthread_join(cid4, NULL);
    pthread_join(cid5, NULL);



    return 0;
}

文章作者: Axieyun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Axieyun !
评论
评论
  目录