十二、多线程

1.什么是线程?

在这里插入图片描述

线程就是进程的执行过程,即进程内部的控制序列,或者说是进程中的一个任务。

一个进程可以同时拥有多个线程,即同时被系统调度的多个执行路径,但至少要有一个主线程——main函数及被其调用的其它函数。

一个进程的所有线程都共享进程的代码区、数据区、BSS区、堆区、环境变量和命令行参数区、文件描述符表、信号处理函数、当前工作目录、用户和组的各种ID等。但是栈区不是共享的,一个进程的每个线程都拥有自己独立的栈区。

线程调度:

(1) 系统内核中专门负责线程调度的处理单元被称为调度器;

(2) 调度器将所有处于就绪状态(没有阻塞在任何系统调用上)的线程排成一个队列,即所谓就绪队列;

(3) 调度器从就绪队列中获取队首线程,为其分配一个时间片,并令处理器执行该线程,过了一段时间:
  A.该线程的时间片耗尽,调度器立即终止该线程,并将其排到就绪队列的尾端,    接着从队首获取下一个线程;
  B.该线程的时间片未耗尽,但需阻塞于某系统调用,比如等待I/O或者睡眠。调度    器会中止该线程,并将其从就绪队列中移至等待队列,直到其等待的条件满足    后,再被移回就绪队列;

(4) 在低优先级线程执行期间,有高优先级线程就绪,后者会抢占前者的时间片;

(5) 若就绪队列为空,则系统内核进入空闲状态,直至其非空;

(6) 像Linux这样的多任务分时系统,基本的调度单位是线程;

(7) 为线程分配的时间片不宜过长,因为时间片太长会导致没有获得处理机会的线程等候时间过久,降低系统运行的并行性,用户会感觉到明显的响应延迟;时间片也不宜过短,因为过短的时间片会增加在线程之间切换上下文的频率,降低系统的运行性能。

2.线程的基本特点

(1)线程是进程中的独立实体,可以拥有自己的资源,可以被独立标识——线程ID,同时也被作为基本调用单元,参数时间片的分配。

(2)线程有不同的状态,如创建、运行、终止、暂停、恢复、取消等。

(3)线程可以使用的大部分资源还是隶属于进程的,因此线程作为进程的一部分不能脱离进程独立存在。

(4)一个进程可以同时执行多个线程,这些线程可以执行相同的代码,完成相同的任务,也可以执行不同的代码,完成不同的任务。

(5)创建一个线程所花费的开销远小于创建进程的开销。线程也称为轻量级进程。因此在解决诸如并发问题等问题时,优先考虑多线程,其次才是多进程。

(6)多线程的问题在于因为太多的资源被共享,极易导致冲突,为了解决冲突可能需要增加额外的开销,因此多进程仍然有它的优势。
进程,内存壁垒,通信。
线程,内存共享,同步。

3.POSIX线程

使用#include <pthread.h>
gcc编译采用 -lpthread 链接 libpthread.so 动态库

4.创建线程

线程过程函数:
在一个线程中被内核调用的函数,对该函数的调用过程就是线程的执行过程,从该函数中返回就意味着线程的结束。因此,main函数其实就是一个进程的主线程的线程过程函数。所有自创建的线程都必须有一个对应线程过程函数。

格式如下:
void* 线程过程函数(void* 线程参数指针) 
{
  线程的执行过程
}

pthread_create函数

用pthread_create创建一个新线程

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

成功返回0,失败返回错误码。
tid - 输出线程标识(TID)。
attr - 线程属性,NULL表示缺省属性。
start_routine - 线程过程函数指针
arg - 线程过程函数的参数指针

被创建的子线程和创建该子线程的父线程是并行的关系,其调度顺序无法预知,因此当pthread_create函数返回时子线程执行的位置无从确定,其线程过程函数可能尚未被调用,也可能正在执行,甚至可能已经返回。传递给线程的参数对象,一定要在线程过程函数不再使用它的情况下才能被释放。

代码:create.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_proc(void* arg) 
{
    printf("%lu线程:%s\n",
        pthread_self(), (char*)arg);
    return NULL;
}
int main(void) 
{
    pthread_t tid;
    int error = pthread_create(&tid, NULL,
        thread_proc, "我是子线程!");
    if (error) {
        fprintf(stderr, "pthread_create: %s\n",
            strerror(error));
        return -1;
    }
    printf("%lu线程:我是主线程,"
        "创建%lu线程。\n", pthread_self(), tid);
    sleep(1);//防止主线程已经结束返回
    return 0;
}

主线程和通过pthread_create函数创建的多个子线程,在时间上“同时”运行,如果不附加任何同步条件,则它们每一个执行步骤的先后顺序完全无法预知,这就叫做自由并发。

代码:concur.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_proc(void* arg) 
{
    for (;;) {
        printf("%d", (int)arg);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
int main(void) 
{
    srand(time(NULL));
    setbuf(stdout, NULL);
    pthread_t tid;
    for (int i = 0; i < 5; ++i)
        pthread_create(&tid, NULL, thread_proc,
            (void*)(i+1));
    getchar();
    return 0;
}

为了让线程过程函数的实现更加灵活,可以通过线程参数传递特定的信息,帮助线程过程函数执行不同的任务。

代码:arg.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <pthread.h>
#define PI 3.14159
void* thread_area(void* arg) 
{
    double r = *(double*)arg;
    *(double*)arg = PI * r * r;
    return NULL;//返回是返回到内核中,所以要获取这里的面积可通过参数arg带回
}
struct Pyth {
    double a, b, c;
};
void* thread_pyth(void* arg) //求三角形斜边
{
    struct Pyth* pyth = (struct Pyth*)arg;
    pyth->c = sqrt((pyth->a *
        pyth->a + pyth->b * pyth->b));
    return NULL;
}
void* thread_aver(void* arg) 
{
    double* d = (double*)arg;
    d[2] = (d[0] + d[1]) / 2;
    return NULL;
}
void* thread_show(void* arg) 
{
    printf("%d\n", *(int*)arg);
    return NULL;
}
int main(void) 
{
    pthread_t tid;
    double r = 10;
    pthread_create(&tid, NULL, thread_area, &r);
    usleep(100000);
    printf("%g\n", r);
    struct Pyth pyth = {3, 4};
    pthread_create(&tid, NULL, thread_pyth,
        &pyth);
    usleep(100000);
    printf("%g\n", pyth.c);
    double d[3] = {123, 456};
    pthread_create(&tid, NULL, thread_aver,
        d);
    usleep(100000);
    printf("%g\n", d[2]);
    int* n = malloc(sizeof(int));
    *n = 1234;
    pthread_create(&tid, NULL, thread_show,
        n);
    usleep(100000);
    free(n);
    return 0;
}

5.汇合线程(清理工作)

在这里插入图片描述

phread_join函数

用于等待子线程终止,清理线程的资源,获得线程过程函数的返回值

int phread_join(pthread_t tid, void** retval);

成功返回0,失败返回错误码。
tid - 线程标识
retval - 线程退出码,接收线程过程函数的返回值

当调用pthread_join函数时:
tid线程已经终止,立刻返回,返回线程退出码。
tid线程尚未终止,阻塞等待,直到被汇合线程终止。

代码:join.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <pthread.h>
#define PI 3.14159
void* thread_area(void* arg) 
{
    double r = *(double*)arg;
    double* s = malloc(sizeof(double));
    *s = PI * r * r;
    return s;
}
int main(void) 
{
    pthread_t tid;
    double r = 10;
    pthread_create(&tid, NULL, thread_area, &r);
    double* s;
    pthread_join(tid, (void**)&s);
    printf("%g\n", *s);
    free(s);
    return 0;
}

6.分离线程

在有些时候作为子线程的创建者,父线程可能并不关心子线程何时终止,同时父线程也不需要获得子线程的返回值。这种情况下,就可将子线程设置为分离线程,这样的线程一旦终止,它们的资源会被系统自动回收,而无需在其父线程中调用pthread_join函数。

pthread_detach函数

int pthread_detach(pthread_t tid);

成功返回0,失败返回错误码。
tid - 线程标识

代码:detach.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_proc(void* arg) 
{
    for (int i = 0; i < 200; ++i) {
        putchar('-');
        usleep(50000);
    }
    return NULL;
}
int main(void) 
{
    setbuf(stdout, NULL);
    //--------自定义线程属性-------------------
    pthread_attr_t attr;
    pthread_attr_init(&attr); // 用缺省值初始化线程属性
    pthread_attr_setdetachstate(&attr,// 设置线程分离状态
        PTHREAD_CREATE_DETACHED);
    //----------------------------------------
    pthread_t tid;
    pthread_create(&tid, /*NULL*/&attr,
        thread_proc, NULL);
    pthread_attr_destroy(&attr);// 对线程进行销毁
    //pthread_detach(tid);在此进行线程分离可能线程已经结束,才执行到此语句
    //					  因此采用自定义线程属性时就设定线程的分离状态可以避免该情况
    /*int error = pthread_join(tid, NULL);
    if (error)
        fprintf(stderr, "pthread_join: %s\n",
            strerror(error));*/
    for (int i = 0; i < 200; ++i) {
        putchar('+');
        usleep(100000);
    }
    printf("\n");
    return 0;
}

7.线程ID

通过pthread_self函数返回的线程ID和pthread_create函数输出的线程ID一样都是由PTHREAD库内部维护的虚拟(伪)线程ID,可用于其它需要提供线程ID的PTHREAD函数。

系统内核维护的真实线程ID,可通过syscall(SYS_gettid)获得:

#include <unistd.h> // 声明syscall函数
#include <syscall.h> // 定义SYS_gettid 宏在Linux系统中,一个进程的PID实际上就是其主线程的TID。
pthread_t tid;
pthread_create(&tid, ...);
               
pthread_self(); -> 返回调用线程的TID

if (tid1 == tid2) // 兼容性不好
    ...
    
int pthread_equal(pthread_t tid1, pthread_t tid2);
两个TID相等返回非零,否则返回0if (pthread_equal(tid1, tid2)) // 兼容性好
代码:equal.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
pthread_t g_main; // 主线程的TID
void foo(void) 
{
    if (pthread_equal(pthread_self(), g_main))
        printf("foo函数在主线程中被调用了。\n");
    else
        printf("foo函数在子线程中被调用了。\n");
}
void* thread_proc(void* arg) {
    printf("系统内核的子线程TID:%ld\n",
        syscall(SYS_gettid));
    foo();
    return NULL;
}
int main(void) 
{
    g_main = pthread_self();
    printf("POSIX库的主线程TID:%lu\n", g_main);
    printf("系统内核的主线程TID:%ld\n",
        syscall(SYS_gettid));
    printf("进程的PID:%d\n", getpid());
    foo();
    pthread_t tid;
    pthread_create(&tid, NULL, thread_proc,
        NULL);
    pthread_join(tid, NULL);
    return 0;
}

8.终止线程(自己)

(1)从线程过程函数中返回,执行该过程函数的线程即终止。其返回值可通过pthread_join函数的第二参数输出给调用者。

(2)在线程过程函数及被其调用的任何函数中都可以调用pthread_exit函数终止当前线程:

pthread_exit函数

void pthread_exit(void* retval);

该函数的参数retval就相当于线程过程函数的返回值,同样可被pthread_join的第二个参数输出给调用者。
注意:在子线程中调用pthread_exit,只会终止调用线程自己,对其其它兄弟线程和主线程没有影响。但如果在主线程中调用pthread_exit,被终止的将是整个进程及其所包含的全部线程。

代码:exit.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <pthread.h>
#define PI 3.14159
void calc_area(double r) 
{
    double* s = malloc(sizeof(double));
    *s = PI * r * r;
    pthread_exit(s);
}
void* thread_area(void* arg) 
{
    printf("调用calc_area函数...\n");
    calc_area(*(double*)arg);
    printf("从calc_area函数返回。\n");
    return NULL;
}
int main(void) 
{
    pthread_t tid;
    double r = 10;
    pthread_create(&tid, NULL, thread_area, &r);
    //pthread_exit(NULL);
    double* s;
    pthread_join(tid, (void**)&s);
    printf("%g\n", *s);
    free(s);
    return 0;
}

9.取消(其它)线程

pthread_cancel函数

int pthread_cancel(pthread_t tid);

成功返回0,失败返回错误码。
tid - 被取消线程的TID。

该函数只是向特定线程发出取消请求,并不等待其终止运行。缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到达到某个取消点。在取消点处,线程检查其自身是否已被取消,若是则立即终止。取消点通常出现在一些特定的系统调用中。

pthread_setcancelstate函数

设置调用线程的取消状态为接受或忽略取消请求:

int pthread_setcancelstate(int state, int* oldstate);

state - 取消状态,可取以下值:
PTHREAD_CANCEL_ENABLE - 接受取消请求(缺省)
PTHREAD_CANCEL_DISABLE - 忽略取消请求
old_state - 输出原取消状态,可取NULL。

pthread_setcanceltype函数

设置调用线程的取消类型为延迟取消或立即(异步)取消:

int pthread_setcanceltype(int type, int* oldtype);

type - 取消类型,可取以下值:
   PTHREAD_CANCEL_DEFERRED - 延迟取消(缺省),接到取消请求,如果不是忽略的话,继续运行一段                    时间,直到执行取消点时再终止。
   PTHREAD_CANEL_ASYNCHRONOUS - 立即取消,接到取消请求。如果不是忽略的话,立刻终止运行。
old_type - 输出原取消类型,可取NULL。

代码:cancel.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
void elapse(void) 
{
    for (unsigned int i = 0; i < 800000000; ++i);
}
void* thread_proc(void* arg) 
{
    /*
    // 禁止接受取消请求
    pthread_setcancelstate(
        PTHREAD_CANCEL_DISABLE, NULL);
    */
    // 取消类型立即取消
    pthread_setcanceltype(
        PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    for (;;) {
        printf("线程:子在川上曰,"
            "逝者如斯夫。\n");
        elapse();
    }
    return NULL;
}
int main(void) 
{
    setbuf(stdout, NULL);
    pthread_t tid;
    pthread_create(&tid, NULL, thread_proc,
        NULL);
    getchar();
    printf("向线程发送取消请求...\n");
    pthread_cancel(tid);
    printf("等待线程终止...\n");
    pthread_join(tid, NULL);
    printf("线程已终止。\n");
    return 0;
}

10.线程冲突

g = 0

无乱序情况下:
      线程1                 线程2
把内存(g=0)中的值读入寄存器(eax=0)
把寄存器(eax=0->1)中的值加1
把寄存器(eax=1)中的值存入内存(g=1)
                  把内存(g=1)中的值读入寄存器(eax=1)
                  把寄存器(eax=1->2)中的值加1
                  把寄存器(eax=2)中的值存入内存(g=2)
乱序情况下:
      线程1                 线程2
把内存(g=0)中的值读入寄存器(eax=0)
                  把内存(g=0)中的值读入寄存器(eax=0)
把寄存器(eax=0->1)中的值加1
                  把寄存器(eax=0->1)中的值加1
把寄存器(eax=1)中的值存入内存(g=1)
                  把寄存器(eax=1)中的值存入内存(g=1)

当两个或两个的线程同时以非原子化的方式访问同一个对象时,极有可能导致对象的最终状态不稳定,这就是所谓的线程冲突。解决冲突的基本原则就是敏感操作的原子化,即保证一个线程完成这组敏感操作以后,再允许另个线程执行类似的操作,位于与共享资源有关的操作代码,在任何时候都只允许一个线程执行。

代码:vie.c线程冲突
#include <stdio.h>
#include <string.h>
#include <pthread.h>
unsigned int g = 0;
void* thread_proc(void* arg) 
{
    for (unsigned int i = 0; i < 100000000; ++i)
        ++g;
    return NULL;
}
int main(void) 
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc,
        NULL);
    pthread_create(&t2, NULL, thread_proc,
        NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("g = %u\n", g);
    return 0;
}

11.互斥锁

互斥锁在保证数据一致性和防止并发冲突的同时,也牺牲了多线程应用的并发性,因此在设计上,应尽量少地使用互斥锁,或者说只有在需要被互斥锁保护的操作过程中,使用它,而对于保护需求不明显的场合尽量不用。

静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-----------------------------------------------------------
动态初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); // 分配内核资源,第二参数为附加属性,这里NULL为默认
...
pthread_mutex_destroy(&mutex); // 释放内核资源
pthread_mutex_lock(&mutex); // 锁定互斥锁
任何时刻只会有一个线程对特定的互斥锁锁定成功,
其它试图对其锁定的线程会在此函数中阻塞等待,
直到该互斥锁的持有者线程将其解锁为止。
pthread_mutex_unlock(&mutex); // 解锁互斥锁
对特定互斥锁对象锁定成功的线程通过此函数将其解锁,
那些阻塞于对该互斥锁对象试图锁定的线程中的一个会被唤醒,
得到该互斥锁,并从被阻塞的pthread_mutex_lock函数中返回。
互斥锁操作流程
void* thread1(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    执行操作,访问共享对象
    pthread_mutex_unlock(&mutex);
    ...
}
void* thread2(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    执行操作,访问共享对象
    pthread_mutex_unlock(&mutex);
    ...
}
代码:mutex.c静态初始
#include <stdio.h>
#include <string.h>
#include <pthread.h>
unsigned int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc(void* arg) 
{
    //pthread_mutex_lock(&m);
    for (unsigned int i = 0; i < 100000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    //pthread_mutex_unlock(&m);
    return NULL;
}
int main(void) 
{
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc,
        NULL);
    pthread_create(&t2, NULL, thread_proc,
        NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %u\n", g);
    return 0;
}
代码:mutex2.c动态初始
#include <stdio.h>
#include <string.h>
#include <pthread.h>
int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc1(void* arg) 
{
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
void* thread_proc2(void* arg) 
{
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        --g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
int main(void) 
{
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1,
        NULL);
    pthread_create(&t2, NULL, thread_proc2,
        NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %d\n", g);
    return 0;
}

12.信号量

功能和用法与XSI/IPC对象中的信号量集非常类似,但是XSI/IPC对象中的信号量集只能用于进程,而这里的信号量既可以用于进程,也可以用于线程。

sem_init函数

初始化信号量

#include <semphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);

成功返回0,失败返回-1。
sem - 信号量。
pshared - 用0和非0分别表示该信号量用于线程或是进程。
value - 信号量的初值,即初始空闲资源数。

当pshared参数取0,信号量仅用于一个进程中的多个线程,其本质就是一个普通的全局变量,但如果改参数取值为非0,则信号量可用于不同的进程,其存储位置在可为这些进程所访问的共享内存中。

sem_destroy函数

销毁信号量

int sem_destroy(sem_t* sem);

成功返回0,失败返回-1。
sem - 信号量。

sem_wait函数

等待(获取)信号量
int sem_wait(sem_t* sem);
成功返回0,失败返回-1。

如果信号量的当前值大于0,则将其减1并立即返回0,表示获得资源。如果该信号量的当前值等于0,这就意味着已经没有空闲资源可供分配,调用该该函数的线程或进程将被阻塞,直到因资源被释放信号量的值够减1为止。

sem_post函数

释放信号量

int sem_post(sem_t* sem);

成功返回0,失败返回-1。

将信号量的值加1。那些正阻塞于针对该信号量的sem_wait函数调用中的一个线程或进程将被唤醒,并在对信号量减1之后从sem_wait函数中返回。

sem_getvalue函数

获取信号量的当前值

int sem_getvalue(sem_t* sem, int* sval);

成功返回0,失败返回-1。
sem - 信号量
sval - 输出信号量的当前值,即当前可分配资源数

代码:sem.c信号量实现数据连接池
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX_CONNS  5 // 最大连接数
#define MAX_USERS 50 // 最大用户数
sem_t s;
void* thread_user(void* arg) 
{
    pthread_t tid = pthread_self();
    int sval;
    sem_getvalue(&s, &sval);
    printf("%lu线程:等待数据连接"
        "(还剩%d个空闲连接)...\n", tid, sval);
    sem_wait(&s);
    sem_getvalue(&s, &sval);
    printf("%lu线程:获得数据连接"
        "(还剩%d个空闲连接)...\n", tid, sval);
    usleep(1000000);
    sem_post(&s);
    sem_getvalue(&s, &sval);
    printf("%lu线程:释放数据连接"
        "(还剩%d个空闲连接)...\n", tid, sval);
    return NULL;
}
int main(void) 
{
    pthread_t tids[MAX_USERS];
    sem_init(&s, 0, MAX_CONNS);
    for (int i = 0; i < sizeof(tids) / sizeof(
        tids[0]); ++i)
        pthread_create(&tids[i], NULL,
            thread_user, NULL);
    for (int i = 0; i < sizeof(tids) / sizeof(
        tids[0]); ++i)
        pthread_join(tids[i], NULL);
    sem_destroy(&s);
    return 0;
}

13.死锁

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t b = PTHREAD_MUTEX_INITIALIZER;
void* thread_proc1(void* arg) 
{
    printf("线程1:加锁a...\n");
    pthread_mutex_lock(&a);
    printf("线程1:加锁a成功!\n");
    sleep(1);
    printf("线程1:加锁b...\n");
    pthread_mutex_lock(&b);
    printf("线程1:加锁b成功!\n");
    // ...
    pthread_mutex_unlock(&b);
    printf("线程1:解锁b。\n");
    pthread_mutex_unlock(&a);
    printf("线程1:解锁a。\n");
    return NULL;
}
void* thread_proc2(void* arg) 
{
    printf("线程2:加锁b...\n");
    pthread_mutex_lock(&b);
    printf("线程2:加锁b成功!\n");
    sleep(1);
    printf("线程2:加锁a...\n");
    pthread_mutex_lock(&a);
    printf("线程2:加锁a成功!\n");
    // ...
    pthread_mutex_unlock(&a);
    printf("线程2:解锁a。\n");
    pthread_mutex_unlock(&b);
    printf("线程2:解锁b。\n");
    return NULL;
}
int main(void) 
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1,
        NULL);
    pthread_create(&t2, NULL, thread_proc2,
        NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("完成!\n");
    return 0;
}

死锁的四个必要条件:

1)独占排它:

线程以独占的方式使用其所获得的资源,即在一段时间内不允许其它线程使用该资源。这段时间内,任何试图请求该资源的线程只能在阻塞中等待,直到资源被其拥有者主动释放。

2)请求保持:

线程已经拥有了至少一份资源,但又试图获取已被其它线程拥有的资源,因此只能在阻塞中等待,同时对自己已经获取的资源又坚守不放。

3)不可剥夺:

线程已经获得的资源,在其未被使用完之前,不可被强制剥夺,而只能由其拥有者自己释放。

4)循环等待:

线程集合{T0,T1,Tn}中,T0等待T1占有的资源,T1等待T2占有的资源,…,Tn等待T0占有的资源,形成环路。

14.条件变量

静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-------------------------------------------------------------------------
动态初始化
pthread_cond_init(&cond, NULL);
...
pthread_cond_destroy(&cond);

pthread_cond_wait函数

等待条件变量,即在条件变量中睡眠

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

当调用线程在cond条件变量中睡眠期间mutex互斥会被解锁(防止互斥锁一直锁着造成死锁),直到该线程从条件变量中苏醒,即从该函数中返回再重新拥有该互斥锁。

pthread_cond_signal函数

int pthread_cond_signal(pthread_cond_t* cond);

唤醒在条件变量cond中睡眠的第一个线程,即条件等待队列的队首线程,待其重新获得互斥锁以后从pthread_cond_wait函数中返回。

pthread_cond_broadcast函数

int pthread_cond_broadcast(pthread_cond_t* cond);

唤醒在条件变量cond中睡眠的所有线程,只有重新获得互斥锁的线程会从pthread_cond_wait函数中返回。

1)简单理解生产者-消费者模型

假设有两个进程(或线程)A、B和一个固定大小的缓冲区,A进程生产数据放入缓冲区,B进程从缓冲区中取出数据进行计算,这就是一个简单的生产者-消费者模型。这里的A进程相当于生产者,B进程相当于消费者。

在这里插入图片描述

2)为什么要使用生产者-消费者模型

在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完数据才能够继续生产数据,因为生产过多的数据可能会导致存储不足;同理如果消费者的速度大于生产者那么消费者就会经常处理等待状态,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区用来存储生产者生产的数据,所以就引入了生产者-消费者模式

简单来说,这里缓冲区的作用就是为了平衡生产者和消费者的数据处理能力,一方面起到缓存作用,另一方面达到解耦合作用。

3)生产者-消费者模型特点

保证生产者不会在缓冲区满的时候继续向缓冲区放入数据,而消费者也不会在缓冲区空的时候,消耗数据。

当缓冲区满的时候,生产者会进入休眠状态,当下次消费者开始消耗缓冲区的数据时,生产者才会被唤醒,开始往缓冲区中添加数据.

当缓冲区空的时候,消费者也会进入休眠状态,直到生产者往缓冲区中添加数据时才会被唤醒。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_STOCK 5 // 仓库容量
char storage[MAX_STOCK]; // 仓库
int stock = 0; // 当前库存
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
// 非满条件变量
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
// 非空条件变量
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
// 显示库存
void show(char const* who, char const* op,
    char prod) 
{
    printf("%s:", who);
    for (int i = 0; i < stock; ++i)
        printf("%c", storage[i]);
    printf("%s%c\n", op, prod);
}
// 生产者线程
void* producer(void* arg) 
{
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == MAX_STOCK) {
            printf("\033[;;32m%s:满仓!"//红色字符打印
                "\033[0m\n", who);
            pthread_cond_wait(&full, &mutex);
        }
        char prod = 'A' + rand() % 26;
        show(who, "<-", prod);
        storage[stock++] = prod;
        //pthread_cond_signal(&empty);
        pthread_cond_broadcast(&empty);
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
// 消费者线程
void* customer(void* arg) 
{
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == 0) {
            printf("\033[;;31m%s:空仓!"//绿色字符打印
                "\033[0m\n", who);
            pthread_cond_wait(&empty, &mutex);
        }
        char prod = storage[--stock];
        show(who, "->", prod);
        //pthread_cond_signal(&full);
        pthread_cond_broadcast(&full);//如果判断条件用if,可能会造成上或下溢出。因此改用while
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
int main(void) 
{
    srand(time(NULL));
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,
        PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    pthread_create(&tid, &attr, producer,
        "生产者1");
    pthread_create(&tid, &attr, producer,
        "生产者2");
    pthread_create(&tid, &attr, customer,
        "消费者1");
    pthread_create(&tid, &attr, customer,
        "消费者2");
    getchar();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值