为了并发的执行任务(程序),现代操作系统特地引入了“进程”的概念
分析:
1. 进程的地址空间是独立的,进程间通信的代价比较大
如果进程需要进行数据的交换,则需要用到进程间通信(pipe/fifo/shm/sem/...)
copy copy
p1进程 --------> 内核 --------> p2进程共享内存:一次拷贝
2. 创建一个进程的系统开销比较大,因为要拷贝整个父进程的地址空间("copy on write")
于是,有人就提出一个想法,能不能在同一个进程地址空间内部实现“任务(程序)”的并发执行呢?
=====>线程
进程是系统资源分配的最小单位,线程是系统调度的最小单位线程/进程 都是 并发 的实体。在 单线程 的情况下,任务 == 进程 == 线程
1. 线程的概念
1)线程是比进程更小的活动单位,它是进程中一个执行路径(执行分支),线程是依附于一个进程的(线程又称轻量级进程)
2)进程内部可以有多个线程,它们之间并发执行,进程内部的所有线程共享进程的地址空间 (对于线程内部空间而言是独立的)
线程的特点:
1. 创建一个线程比创建一个进程的开销要小得多
因为不要拷贝父进程的地址空间,线程不拥有系统资源,只有一点点在运行中不可缺少的数据结构,但可以访问其隶属进程的资源。同一进程中的所有线程都具有相同的地址空间(进程的地址空间)
进程是分配系统资源的最小单位2. 实现线程间通信十分方便,因为进程内部的所有线程共享进程的地址空间。线程之间的通信不需要调用内核
3. 线程也是一个动态的概念(ready / running / blocking)
是进程内部的一个执行分支,线程是用来并发执行进程内部的指令的
C语言的指令必须在函数内部====>所有线程对应一个线程函数,线程的工作就是去执行指定的函数,如果指定的函数执行完了,线程也完了
4. 一个进程里面默认有一个主线程 (main函数),在进程的运行过程中,可以创建其他的子线程,其余分支我们称之为分线程。主函数结束同样意味着进程结束 (所有子线程都会强制退出)
5. 进程是分配系统资源的最小单位,系统是按照线程来进行调度的 (一个线程就是一个任务,需要分配CPU)
在进程内部创建多个线程,可以提高进程的CPU占有率
Thread的实现方式有很多种,比较常用的是POSIX标准的线程
线程所有的API函数都需要链接多线程库 (libpthread.so)
gcc test.c -o test -lpthread
二. linux下面线程(thread)函数的API
1)创建一个线程:pthread_create
线程有一个线程ID (tid,thread id),类似于进程id (pid)
用来唯一的标识一个线程的,在pthread中,使用类型 pthread_t 来描述一个线程ID
typedef unsigned long pthread_t;
线程属性 (pthread_attr_t)
线程的id
线程的优先级
线程的栈空间大小"stack"
....
在pthread中,线程属性使用pthread_attr_t (结构体)来描述,同时还提供了几个用于改变线程属性的APi函数,但是不建议程序员直接修改pthread_attr_t 的结构体,而是使用“线程默认属性”线程是进程内部的一个指令的执行分支,多个线程,就是多个指令序列并发的执行
C语言的指令必须在函数内部,所有线程对应一个线程函数,一个线程创建成功后,要执行的指令序列全部都在一个指定的函数中 (“线程函数”),这个线程函数执行完毕了,线程的任务结束了
指定线程的执行函数??? 如何指定
指定线程函数的地址,线程函数的地址作为参数传入创建线程的API,问题:张三的线程函数:
int func(int) {
......
}
李四的线程函数
void *fun(void) {
......
}
.......
线程函数的地址 (函数指针)作为参数传入,函数指针也是有类型的,所以规定线程函数的地址类型必须为:
void* (*start_routine) (void *)
start_routine:函数指针变量,保存一个函数的地址,指向一个函数,这个函数的类型应该是:
void* func(void *) {
}
====>
所以线程函数都必须有一个void*的返回值,有一个void*的参数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); thread:指针(pthread_t *), 指向一块可用的空间,用来保存新创建的线程的线程id 线程的线程Id可以使用pthread_self()获取 NAME pthread_self - obtain ID of the calling thread SYNOPSIS #include <pthread.h> pthread_t pthread_self(void); Compile and link with -pthread. 获取调用者自己的线程ID attr:指针,指向一个pthread_attr_t的结构体,此结构体保存的是新创建的线程的属性, 一般使用NULL表示使用默认属性 start_routine:函数指针变量,指向你要执行的指令序列(函数),新创建的线程的任务就是去执行 start_routine指向的函数,函数执行完毕,线程就结束了 arg:void *,线程函数的实际参数。 如果线程函数不需要参数,可以写NULL, 如果线程函数有一个参数,可以传入一个指针 如果线程函数有多个参数呢? 变成一个参数,封装成结构体 struct arg { int a; int b; }; 返回值: 成功返回0 失败返回错误号,并且thread参数所保存的内容是无意义的 新线程执行由系统任务调度决定 Compile and link with -pthread. pthread_t tid; pthread_create(&tid, NULL, my_thread, NULL);
2)获取调用者自己的线程ID:pthread_self
NAME pthread_self - obtain ID of the calling thread SYNOPSIS #include <pthread.h> pthread_t pthread_self(void); 获取调用者自己的线程ID Compile and link with -pthread.
#include <stdio.h> #include <pthread.h> #include <error.h> #include <unistd.h> struct arg { int a; int b; }; // 线程函数,子线程需要指向的指令序列 // 有一个void*的参数,有一个void*的返回值 void* my_routine(void *arg) { // 在pthread_create中封装成void*类型,在线程函数中还原 // 通过地址去访问进程地址空间中的对象 int *p = (int *)arg; *p = 250; // struct arg *t = (struct arg *)arg; // printf("t->a = %d,t->b = %d\n", t->a, t->b); printf("my tid = %lu\n", pthread_self()); while (1) { printf("*p = %d,hello\n", *p); printf("hello\n"); sleep(1); } } int main(int argc, char *argv[]) { pthread_t tid; // 保存子线程的id int x = 1024; // 作为线程函数的参数,只能传入地址 int *p1 = &x; int a = 1; int b = 2; struct arg test; test.a = a; test.b = b; int r = pthread_create(&tid, // 用来保存创建的线程的ID NULL, // 线程属性,NULL表示使用默认属性 my_routine, // 函数指针,函数的地址 // (void *)&x); // 线程函数的参数 (void *)p1); // (void *)&test); if (-1 == r) { perror("pthread_create error"); return -1; } printf("son thread tid = %lu\n", tid); while (1) { printf("x = %d,world\n", x); sleep(1); } // 线程是依附于进程的,进程结束会造成所有子线程结束 return 0; }
3)进程与线程的区别
进程 和 线程 都拥有 自身的PCB (进程控制块) ----->线程得以被调度
对于fork而言:父子进程的PCB成员对应的空间是独立的
对于pthread_create而言:主线程和分线程的PCB成员对应的空间是指向的相同的空间
进程视角中 任务以pid的形式展开,线程视角中 任务以tid的形式展开
top:进程视角 top -H:线程视角 /proc 该目录下是任务的实体文件,对于多线程任务而言,该目录下的task目录下, 存放了线程任务的实体文件
PCB中成员所指向的空间是独立的,则称之为进程;PCB中成员所指向的空间是相同的,则称之为线程
4)线程退出
4.1 线程函数结束
线程函数返回 / 结束,线程自动退出
4.2 在线程执行的任意时刻,调用 pthread_exit(),线程内部
NAME pthread_exit - terminate calling thread 终止调用者线程 SYNOPSIS #include <pthread.h> void pthread_exit(void *retval); 退出码可以被其他线程读取 retval指向的值不应位于调用线程的栈上,因为在线程终止后该栈的内容是未定义的 malloc() Compile and link with -pthread
4.3 被其他线程干掉了 (It is canceled)
其他线程调用 pthread_cancel(tid)
NAME pthread_cancel - send a cancellation request to a thread SYNOPSIS #include <pthread.h> int pthread_cancel(pthread_t thread); thread:线程号,你要取消的线程的线程号 返回值: 成功返回0 取消不了则失败,返回非0错误号,不会阻塞 Compile and link with -pthread.
t1:pthread_cancel(t2)
t1调用pthread_cancel(t2)取消t2线程,t2线程一般会被 t1 给取消,但是不一定,这个依赖于t2的一个线程属性,它是否可以被cancel,这个属性提供了一个接口去修改它:
pthread_setcancelstate()
NAME pthread_setcancelstate, pthread_setcanceltype - set cancelability state and type SYNOPSIS #include <pthread.h> int pthread_setcancelstate(int state, int *oldstate); state:要设置的“取消属性”的值,有以下两种 PTHREAD_CANCEL_ENABLE <---- 默认属性 该线程可以被其他线程取消 PTHREAD_CANCEL_DISABLE 该线程不可以被其他线程取消 如果收到取消请求,则请求会累积到下一次线程设置为可取消状态为止,线程退出 oldstate:指针,指向一个可用的空间,用来保存上一次的“取消属性的”状态值 返回值: 成功返回0 失败返回非0错误号
一个线程退出了,并不代表它所有的资源都被释放掉了
一个线程的退出,它的资源是否全部被释放,取决于它的一个属性:
detach 分离属性
ENABLE 分离状态
该线程结束,它所有的资源就会自动的释放
DISABLE 非分离 <------ 默认状态
该线程结束,它会有部分资源不会自动释放,必须要其他线程调用pthread_join这个函数才能释放
pthread_detach 用来设置一个线程的分离属性 (设置为分离)
线程的分离属性:分离属性,能够让系统自动回收线程结束后的资源
NAME pthread_detach - detach a thread SYNOPSIS #include <pthread.h> int pthread_detach(pthread_t thread); thread:你要设置为分离状态的线程ID 返回值: 成功返回0 失败返回错误码 Compile and link with -pthread.
如果线程是非分离状态的,在线程结束的时候,需要pthread_join函数去回收线程的资源,并且读取退出信息 (pthread_exit(retval))
一个分离的线程是不需要调用pthread_join的,否则会失败,该分离的线程会退出,pthread_join返回一个错误号
pthread_join 等待 (并且释放资源)一个线程退出NAME pthread_join - join with a terminated thread SYNOPSIS #include <pthread.h> // pthread_join会阻塞自己,直到等待的线程退出 // 已设置分离状态的线程不能被pthread_join int pthread_join(pthread_t thread, void **retval); thread:要等待退出的那个线程的线程id(tid) retval:类型是void ** 二级指针,用来保存退出线程的返回状态的 把线程的返回值保存到retval指向的内存中去,只不过retval是一个二级指针, 指向了一个“一级指针”,因为线程的返回值本来就是一个一级指针 如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值, 可以将 retval 参数置为 NULL note:线程返回的一级指针,最好不要是局部变量 返回值: 成功返回0 失败返回对应的错误码,如果线程已经结束,那么该函数会立即返回 作用: 1.等待线程退出 2.回收被等待的线程的资源 Compile and link with -pthread.
为什么是二级指针? void *func(void *arg) { int *p = malloc(); // 重要的信息 return (void *)p; } 线程的函数的返回值 void *, 而且pthread_exit的参数也是void *retval retval保存退出信息的 在调用pthread_join前要准备一个变量接收线程的返回值 void *r; // 接收返回值 r = p; 由于作用域的限制,在pthread_join内不能直接访问r变量的,只能通过r的地址去访问r ===>传入r的地址 pthread_join(, &r); &r ===> void ** // 指针的地址 void *threadReturn; // ... 创建线程并等待它 ... if (pthread_join(thread, &threadReturn) == 0) { if (threadReturn != NULL) { int* actualResult = (int*)threadReturn; printf("Thread returned: %d\n", *actualResult); // 不要忘记释放之前分配的内存 free(threadReturn); } } // 如果传入threadReturn,一级指针只是赋值,不能让它指向线程里面那个保存退出信息的地址
5)线程之间的同步
线程之间也经常需要访问一些共享资源,为了对共享资源的有序访问,防止竞争,需要对共享资源进行某种方式的保护,使共享资源有序访问 ===> "信号量机制"
任何全局对象,malloc的对象....都可以是共享资源,多个线程都可以同时访问
先确定共享资源
再确定临界区
再使用保护机制去保护5.1 信号量机制
System V 信号量 --->内核
POSIX 信号量
有名信号量 --->内核
无名信号量 --->内核 / 内存 (进程地址空间)
5.2 线程互斥锁线程互斥锁是存在于进程地址空间内部的一种保护机制"类似于信号量",只不过存在于进程内部
在POSIX多线程中,使用 pthread_mutex_t 类型表示一个线程互斥锁的类型,线程互斥锁只能用于进程内部的不同线程之间的同步 (存在于内存中)
线程互斥锁的接口函数:
pthread_mutex_init 初始化一个指定的线程互斥锁
pthread_mutex_destory 销毁指定的线程互斥锁
pthread_mutex_lock P操作
pthread_mutex_unlock V操作
系统默认是没有安装POSIX的手册页包的
很多函数是没有手册页
sudo apt-get install manpages-posix-dev
1. 初始化 / 销毁一个线程互斥锁NAME pthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutex SYNOPSIS #include <pthread.h> pthread_mutex_init用来初始化mutex指向的线程互斥锁 使用线程互斥锁前,先定义一个线程互斥锁的对象 pthread_mutex_t mutex; int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); mutex:要初始化的线程互斥锁 attr:线程互斥锁的属性,一般建议写NULL,表示使用默认属性 返回值: 成功返回0 失败返回非0值,errno被设置 互斥:线程互斥锁默认初始化后的值为1 restrict:C语言的关键字,指定的指针是访问数据对象的唯一的方式。 所有修改该指针指向的内存中的内容的操作都必须通过该指针修改。 如: int *restrict p; // 该语句 也可以初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ----------------------------------------------------------------------- pthread_mutex_destroy:销毁指定的线程互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 初始化一个线程互斥锁 pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); // 该语句 也可以初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 销毁一个线程互斥锁 pthread_mutex_destroy(&mutex);
2. 线程互斥锁的P/V操作
P操作 (lock)
NAME pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock - lock and unlock a mutex SYNOPSIS #include <pthread.h> pthread_mutex_lock:获取mutex指向的线程互斥锁,类似于信号量中的sem_wait,“死等”, 没有获取则阻塞 int pthread_mutex_lock(pthread_mutex_t *mutex); 返回值: 成功返回0,表示获取了该线程互斥锁,可以进入临界区执行代码 失败返回-1,表示出错了,没有获取到互斥锁 不出错也没有获取则“阻塞” ---------------------------------------------------------------------------- pthread_mutex_trylock 尝试获取mutex指向的锁,能够获取则获取,不能获取则立即返回, “非阻塞等待” int pthread_mutex_trylock(pthread_mutex_t *mutex); 返回值: 成功返回0,表示获取了该线程互斥锁,可以进入临界区执行代码 失败返回其他值,没有获取到互斥锁 ---------------------------------------------------------------------- NAME pthread_mutex_timedlock - lock a mutex (ADVANCED REAL-TIME) SYNOPSIS #include <pthread.h> #include <time.h> pthread_mutex_timedlock:限时等待,等待到固定的时间 abs_timeout:绝对超时时间,类似于信号量sem_timedwait,abs_timeout 是未来的一个时间点,指示函数等待到未来的一个时间点如果 还没有获取则立即返回 int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout); 返回值: 返回0,表示获取了该线程互斥锁,可以进入临界区执行代码 返回其他值,没有获取到互斥锁 例子: struct timespec tv; // 获取系统的绝对时间(1970年到现在的秒数) clock_gettime(CLOCK_REALTIME, &tv); tv.tv_sec += 2; pthread_mutex_timedlock(mutex, &tv);
V操作,解锁 (unlock)
pthread_mutex_unlock用来释放mutex指向的线程互斥锁 int pthread_mutex_unlock(pthread_mutex_t *mutex);
6)生产者—消费者模型
存在的几个问题:
1. 生产者和消费者都需要一直访问数据存放的对象,共享资源的互斥问题
信号量/线程互斥锁保护
2. 当数据存放对象 (缓冲区)没有数据时,消费者线程,该怎么办?
a. 不停的去测试,看看有没有数据
“轮询”,但是“轮询”会有天生的缺陷 (不停的测试 if())
浪费CPU
轮询会有一个时间差,反馈不及时
占用总线资源,is always busy
b. 让出CPU(sleep),当有数据的时候,再去唤醒(wake up)我
======>
线程条件变量7)线程条件变量
条件变量 可以使线程在等待相应条件发生时进行休眠 -----> 让出CPU
在多线程程序设计中,我们可以使用一个 "条件变量" 表示一个特定的条件或者事件
pthread_cond_t:条件变量的类型
至于这个条件变量,到底表示什么条件或者什么事件,完全由程序员自己定义
条件变量,代表某个条件或者某个事件,也是共享资源,也需要被保护,一般在写程序的时候,“一对M(mutex)C(cond)”
在条件变量上一般有三种操作:
a. 初始化
b. 等待一个条件变量 (等待该条件变量所表示的事件发生)
c. 唤醒一个条件变量 (唤醒正在等待该事件的线程)1. 条件变量的初始化和销毁
NAME pthread_cond_destroy, pthread_cond_init — destroy and initialize condition variables SYNOPSIS #include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); 返回值: 成功返回0 失败返回其他值(错误码) --------------------------------------------------------------- // pthread_cond_init用来初始化指定的条件变量,使用前必须先定义 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); cond:指针,指向要初始化的条件变量 attr:属性,一般采用默认属性 NULL 返回值: 成功返回0 失败返回其他值(错误码) // 该语句也可以用来初始化条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 初始化一个条件变量 pthread_cond_t cond; pthread_cond_init(&cond, NULL); // 该语句也可以用来初始化条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 销毁一个条件变量 pthread_cond_destroy(&cond);
2. 等待一个条件变量 pthread_cond_wait
NAME pthread_cond_timedwait, pthread_cond_wait — wait on a condition // pthread_cond_wait等待指定的条件变量 “阻塞等待” SYNOPSIS #include <pthread.h> // 限时等待 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); cond:你要等待的那个条件变量 mutex:线程互斥锁 为了保护cond所代表的事件/共享资源的 mutex一般是访问共享资源前获取的锁 pthread_mutex_lock pthread_cond_wait 休眠之前要进行解锁 睡醒之后要进行上锁 pthread_mutex_unlock 注意: 一般在调用pthread_cond_wait时,已经获取了锁 “mutex”是一个锁定的状态.如果wait阻塞了,就可能会造成带锁等待 pthread_cond_wait(&cond, &mutex) { // mutex --> 锁定状态 解锁(mutex); 让出CPU,阻塞; // sleep()... when 条件产生,其他的子线程“wakeup” lock; // --->锁定状态 继续往下运行,访问临界区 } abstime:绝对时间 返回值: 成功返回0(被其他线程唤醒了) 失败返回非0 // 等待一个条件变量 pthread_cond_wait(&cond, &mutex);
3. 唤醒等待条件变量的线程 pthread_cond_signal
NAME pthread_cond_broadcast, pthread_cond_signal — broadcast or signal a condition SYNOPSIS #include <pthread.h> // 广播 的方式 唤醒所有正在等待的线程 // 但是哪一个线程最先能够获取条件,不一定的 int pthread_cond_broadcast(pthread_cond_t *cond); // 只会唤醒一个正在等待的线程 int pthread_cond_signal(pthread_cond_t *cond); PS:告诉线程 你所等待的条件发生了
8)线程条件变量练习
计数器 --- 线程1 每1s进行计数 +1 void *threadConut(void *arg) { sleep(1); num++; } 其他线程竞争计数值 场景一:不使用条件变量 其他线程数量只有一个 每两秒输出一次 场景二:使用条件变量 其他线程数量只有一个 每两秒输出一次 但是没有两秒之前休眠 场景三:使用条件变量 其他线程数量有4个,每两秒输出一次,没有两秒之前休眠 并且2s到达时只能有一个线程输出(唤醒其中一个) 场景四:使用条件变量 其他线程数量有4个,每两秒输出一次,没有两秒之前休眠 并且2s到达时4个线程都被唤醒,只能有一个线程输出
场景一:
/* 场景一:不使用条件变量 其他线程数量只有一个 每两秒输出一次 */ #include <stdio.h> #include <pthread.h> #include <unistd.h> int i = 0; // 初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 线程1 每1s进行计数 +1 void* thread_count(void *arg) { while (1) { sleep(1); // p 上锁 pthread_mutex_lock(&mutex); // 临界区代码 i++; // v 解锁 pthread_mutex_unlock(&mutex); } } void* thread_print(void *arg) { int temp = 0; while (1) { pthread_mutex_lock(&mutex); if (i - temp == 2) { printf("---%d---\n", i); temp = i; } pthread_mutex_unlock(&mutex); } } int main() { pthread_t tid; // 创建一个 thread_count 线程 pthread_create(&tid, NULL, thread_count, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); pthread_join(tid, NULL); return 0; }
场景二:
/* 场景二:使用条件变量 其他线程数量只有一个 每两秒输出一次 但是没有两秒之前休眠 */ #include <stdio.h> #include <pthread.h> #include <unistd.h> int i = 0; int temp = 0; // 初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化一个条件变量:达到两秒输出一次 i 的值 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 线程1 每1s进行计数 +1 void* thread_count(void *arg) { while (1) { sleep(1); // p 上锁 pthread_mutex_lock(&mutex); // 临界区代码 i++; // v 解锁 pthread_mutex_unlock(&mutex); if (i - temp == 2) { // 唤醒 pthread_cond_signal(&cond); } } } void* thread_print(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待 pthread_cond_wait(&cond, &mutex); printf("---%d---\n", i); temp = i; pthread_mutex_unlock(&mutex); } } int main() { pthread_t tid; // 创建一个 thread_count 线程 pthread_create(&tid, NULL, thread_count, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); pthread_join(tid, NULL); return 0; }
场景三:
/* 场景三:使用条件变量 其他线程数量有4个,每两秒输出一次,没有两秒之前休眠 并且2s到达时只能有一个线程输出(唤醒其中一个) */ #include <stdio.h> #include <pthread.h> #include <unistd.h> int i = 0; int temp = 0; // 初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化一个条件变量:达到两秒输出一次 i 的值 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 线程1 每1s进行计数 +1 void* thread_count(void *arg) { while (1) { sleep(1); // p 上锁 pthread_mutex_lock(&mutex); // 临界区代码 i++; // v 解锁 pthread_mutex_unlock(&mutex); if (i - temp == 2) { // 唤醒一个 pthread_cond_signal(&cond); } } } void* thread_print(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待 pthread_cond_wait(&cond, &mutex); printf("---%d---\n", i); temp = i; pthread_mutex_unlock(&mutex); } } int main() { pthread_t tid; // 创建一个 thread_count 线程 pthread_create(&tid, NULL, thread_count, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); pthread_join(tid, NULL); return 0; }
场景四:
/* 场景四:使用条件变量 其他线程数量有4个,每两秒输出一次,没有两秒之前休眠 并且2s到达时4个线程都被唤醒,只能有一个线程输出 */ #include <stdio.h> #include <pthread.h> #include <unistd.h> int i = 0; int temp = 0; int flag = 0; // 初始化一个线程互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化一个条件变量:达到两秒输出一次 i 的值 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 线程1 每1s进行计数 +1 void* thread_count(void *arg) { while (1) { sleep(1); // p 上锁 pthread_mutex_lock(&mutex); // 临界区代码 i++; // v 解锁 pthread_mutex_unlock(&mutex); if (i - temp == 2) { // 唤醒——广播 pthread_cond_broadcast(&cond); } } } void* thread_print(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待 pthread_cond_wait(&cond, &mutex); flag++; if (flag % 4 != 0) { pthread_mutex_unlock(&mutex); continue; } flag = 0; printf("---%d---\n", i); temp = i; pthread_mutex_unlock(&mutex); } } int main() { pthread_t tid; // 创建一个 thread_count 线程 pthread_create(&tid, NULL, thread_count, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); // 创建一个 thread_print 线程 pthread_create(&tid, NULL, thread_print, NULL); pthread_join(tid, NULL); return 0; }