谁教我做啊谁会做网站啊,昆山专业的网站建设,建设工程消防设计备案网站,网站实现中英文第三章 Linux 多线程开发 3.1 线程3.1.2 线程操作3.1.2 线程属性 3.2 线程同步3.2.1 互斥量/锁3.2.2 死锁3.2.3 读写锁 3.3 生产者消费者模型3.3.1 条件变量3.3.2 信号量/灯 网络编程系列文章#xff1a; 第1章 Linux系统编程入门#xff08;上#xff09; 第1章 Linux系统… 第三章 Linux 多线程开发 3.1 线程3.1.2 线程操作3.1.2 线程属性 3.2 线程同步3.2.1 互斥量/锁3.2.2 死锁3.2.3 读写锁 3.3 生产者消费者模型3.3.1 条件变量3.3.2 信号量/灯 网络编程系列文章 第1章 Linux系统编程入门上 第1章 Linux系统编程入门下 第2章 Linux多进程开发上 第2章 Linux多进程开发下 第3章 Linux多线程开发 第4章 Linux网络编程 4.1 网络基础4.2 socket 通信基础4.3 TCP套接字通信4.4 IO多路复用4.5 UDP 通信 第5章 Web服务器 3.1 线程 线程概述 与进程 process 类似线程 ( thread 是允许应用程序 并发执行 多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序且共享同一份全局内存区域 其中包括 初始化数据段、未初始化数据段以及 堆内存段。传统意义上的 UNIX 进程只是多线程程序的一个特例该进程只包含一个线程 进程 是 CPU 分配资源的最小单位线程是操作系统调度执行的最小单位。 线程是轻量级的进程 LWP Light Weight Process 在 Linux 环境下线程的本质仍是进程。 查看指定进程的 LWP 号 ps -Lf pid 例如打开火狐浏览器查看进程所包含的线程 线程和进程区别 进程间的信息难以共享。由于除去只读代码段外父子进程并未共享内存因此必须采用一些进程间通信方式在进程间进行信息交换。调用 fork() 来创建进程的代价相对较高即便利用写时复制技术仍然需要复制诸如 内存页表 和 文件描述符表 之类的多种 进程属性这意味着 fork() 调用在时间上的开销依然不菲。线程之间能够方便、快速地共享信息。只需将数据复制到 共享全局或堆变量 中即可。创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的无需采用写时复制来复制内存也无需复制页表。 线程和进程虚拟地址空间 线程之间共享 和 非共享资源 共享资源 进程 ID 和父进程 ID进程组 ID 和会话 ID用户 ID 和 用户组 ID文件描述符表 这些都是内核中的数据信号处置文件系统的相关信息文件权限掩码 ( umask 、当前工作目录虚拟地址空间除 栈、 .text ) 非共享资源 线程 ID信号掩码线程特有数据error 变量实时调度策略和优先级栈本地变量和函数的调用链接信息 NPTL 简介 当 Linux 最初开发时在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程 calling process 的一个拷贝这个拷贝与调用进程共享相同的地址空间。 LinuxThreads 项目使用这个调用来完成在用户空间模拟对线程的支持。不幸的是这种方法有一些缺点尤其是在信号处理、调度和进程间同步等方面都存在问题。另外这个线程模型也不符合 POSIX 的要求。 要改进 LinuxThreads 需要内核的支持并且重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT ( Next Generation POSIX Threads 项目。同时Red Hat 的一些开发人员开展了 NPTL 项目。 NGPT 在 2003 年中期被放弃了把这个领域完全留给了 NPTL 。 NPTL 或称为 Native POSIX Thread Library 是 Linux 线程的一个新实现它克服了 LinuxThreads 的缺点同时也符合 POSIX 的需求。与 LinuxThreads 相比它在性能和稳定性方面都提供了重大的改进。 查看当前 pthread 库版本getconf GNU_LIBPTHREAD_VERSION
3.1.2 线程操作
// 创建一个子线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);// 获取当前的线程的线程ID
pthread_t pthread_self(void);// 比较两个线程ID是否相等
int pthread_equal(pthread_t t1, pthread_t t2);// 终止一个线程
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);以上命令也可以使用 man pthread 按两次Tab键 / man -k pthread 进行查询 # 如果输入显示 没有 pthread 的手册页条目
# 执行如下两条命令
sudo apt-get install glibc-doc
sudo apt-get install manpages-posix manpages-posix-dev# 再输入
man pthread 按两次Tab键
man -k pthread一般情况下, main 函数所在的线程我们称之为主线程main线程其余创建的线程称之为 子线程。 程序中默认只有一个进程fork() 函数调用会有2进程。主进程、子进程程序中默认只有一个线程pthread_create() 函数调用2个线程。主线程、子线程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能创建 一个子线程 参数 thread传出参数线程创建成功后子线程的线程ID 被写到该变量中。attr : 设置线程的属性一般使用默认值NULLstart_routine : 函数指针这个函数是子线程需要处理的逻辑代码arg : 给第三个参数使用传参 返回值 成功0失败返回错误号。这个错误号和之前 errno 不太一样。 获取错误号的信息 char * strerror(int errnum); #include stdio.h
#include pthread.h
#include string.h
#include unistd.h// 子线程
void * callback(void * arg) {printf(child thread...\n);printf(arg value: %d\n, *(int *)arg);return NULL;
}int main() { // 主线程pthread_t tid;int num 10;// 创建一个子线程int ret pthread_create(tid, NULL, callback, (void *)num);if(ret ! 0) {char * errstr strerror(ret);printf(error : %s\n, errstr);} for(int i 0; i 5; i) {printf(%d\n, i);}sleep(1);return 0; // exit(0); 退出进程
}编译上面对的pthread_create.c文件gcc pthread_create.c -o create -lpthread因为 pthread.h 是第三方库所有要使用 -l库名 链接。⭐️⭐️⭐️ void pthread_exit(void *retval); 功能终止一个线程在哪个线程中调用就表示终止哪个线程参数 retval: 需要传递一个指针作为一个返回值可以在 pthread_join() 中获取到。 pthread_t pthread_self(void); 功能获取 当前的线程的 线程ID int pthread_equal(pthread_t t1, pthread_t t2); 功能比较 两个线程ID是否相等 注意不同的操作系统pthread_t 类型的实现不一样有的是 无符号的长整型有的是使用 结构体 去实现的。 #include stdio.h
#include pthread.h
#include string.hvoid * callback(void * arg) {printf(child thread id : %ld\n, pthread_self());return NULL; // pthread_exit(NULL);
} int main() {// 创建一个子线程pthread_t tid;int ret pthread_create(tid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error : %s\n, errstr);}// 主线程for(int i 0; i 5; i) {printf(%d\n, i);}printf(tid : %ld, main thread id : %ld\n, tid, pthread_self());// 让主线程退出,当主线程退出时不会影响其他正常运行的线程。pthread_exit(NULL);printf(main thread exit\n); // 该句不会运行return 0; // exit(0);
}int pthread_join(pthread_t thread, void **retval); 功能和一个已经终止的线程进行 连接 回收子线程的资源 这个函数是阻塞函数调用一次只能回收一个子线程 一般在主线程中使用参数 thread需要回收的子线程的IDretval: 接收子线程退出时的返回值 (二级指针) 返回值 0 : 成功 非0 : 失败返回的错误号 #include stdio.h
#include pthread.h
#include string.h
#include unistd.hint value 10; //全局变量共享void * callback(void * arg) {printf(child thread id : %ld\n, pthread_self());// sleep(3);// return NULL; // int value 10; // 局部变量该线程退出时就会销毁pthread_exit((void *)value); //等同 return (void *)value;
} int main() {// 创建一个子线程pthread_t tid;int ret pthread_create(tid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error : %s\n, errstr);}// 主线程for(int i 0; i 5; i) {printf(%d\n, i);}printf(tid : %ld, main thread id : %ld\n, tid ,pthread_self());// 主线程调用pthread_join()回收子线程的资源int * thread_retval;ret pthread_join(tid, (void **)thread_retval);if(ret ! 0) {char * errstr strerror(ret);printf(error : %s\n, errstr);}printf(exit data : %d\n, *thread_retval); // 返回的全局变量 如果是子线程中的局部变量会返回随机值因为子线程中的局部变量被收回printf(回收子线程资源成功\n);// 让主线程退出,当主线程退出时不会影响其他正常运行的线程。pthread_exit(NULL);return 0;
} int pthread_detach(pthread_t thread); 功能分离 一个线程。被分离的线程在终止的时候会自动释放资源返回给系统。 不能多次分离会产生不可预料的行为。不能去连接一个已经分离的线程会报错。 参数需要分离的线程的ID 返回值 成功0 失败返回错误号 #include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid * callback(void * arg) {printf(chid thread id : %ld\n, pthread_self());return NULL;
}int main() {// 创建一个子线程pthread_t tid;int ret pthread_create(tid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error1 : %s\n, errstr);}// 输出主线程和子线程的idprintf(tid : %ld, main thread id : %ld\n, tid, pthread_self());// 设置子线程分离,子线程分离后子线程结束时对应的资源就不需要主线程释放ret pthread_detach(tid);if(ret ! 0) {char * errstr strerror(ret);printf(error2 : %s\n, errstr);}// 设置分离后对分离的子线程进行连接 pthread_join()// ret pthread_join(tid, NULL);// if(ret ! 0) {// char * errstr strerror(ret);// printf(error3 : %s\n, errstr);// }pthread_exit(NULL);return 0;
}int pthread_cancel(pthread_t thread); 功能取消 线程让线程终止 取消某个线程可以终止某个线程的运行 但是并不是立马终止而是当子线程执行到一个取消点线程才会终止。 取消点系统规定好的一些 系统调用我们可以粗略的理解为 从 用户区 到 内核区 的切换这个位置称之为取消点。 返回值和上面的相同 #include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid * callback(void * arg) {printf(chid thread id : %ld\n, pthread_self());for(int i 0; i 5; i) {printf(child : %d\n, i); // 也是一个取消点}return NULL;
}int main() {// 创建一个子线程pthread_t tid;int ret pthread_create(tid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error1 : %s\n, errstr);}// 取消线程pthread_cancel(tid);for(int i 0; i 5; i) {printf(%d\n, i);}// 输出主线程和子线程的idprintf(tid : %ld, main thread id : %ld\n, tid, pthread_self());pthread_exit(NULL);return 0;
}3.1.2 线程属性
// 线程属性类型 pthread_attr_t
// 初始化线程属性变量
int pthread_attr_init(pthread_attr_t *attr);// 释放线程属性的资源
int pthread_attr_destroy(pthread_attr_t *attr);// 获取线程分离的状态属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);// 设置线程分离的状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);查看线程属性的操作man pthread_attr_按两次Tab键 #include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid * callback(void * arg) {printf(chid thread id : %ld\n, pthread_self());return NULL;
}int main() {// 创建一个线程属性变量pthread_attr_t attr;// 初始化属性变量pthread_attr_init(attr);// 设置属性pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);// 创建一个子线程pthread_t tid;int ret pthread_create(tid, attr, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error1 : %s\n, errstr);}// 获取线程的栈的大小size_t size;pthread_attr_getstacksize(attr, size);printf(thread stack size : %ld\n, size);// 输出主线程和子线程的idprintf(tid : %ld, main thread id : %ld\n, tid, pthread_self());// 释放线程属性资源pthread_attr_destroy(attr);pthread_exit(NULL);return 0;
}3.2 线程同步
线程的主要优势在于能够通过 全局变量 来共享信息。不过这种便捷的共享是有代价的必须确保多个线程不会同时修改同一变量或者某一线程不会读取正在由其他线程修改的变量。临界区 是指访问某一 共享资源的代码片段并且这段代码的执行应为 原子操作也就是同时访问同一共享资源的其他线程 不应终断 该片段的执行。线程同步即当有一个线程在对内存进行操作时其他线程都不可以对这个内存地址进行操作直到该线程完成操作其他线程才能对该内存地址进行操作而其他线程则处于等待状态。
使用多线程实现买票的案例。 有3个窗口一共是100张票。 #include stdio.h
#include pthread.h
#include unistd.h// 全局变量所有的线程都共享这一份资源。
int tickets 100;void * sellticket(void * arg) {// 卖票while(tickets 0) {usleep(6000);printf(%ld 正在卖第 %d 张门票\n, pthread_self(), tickets);tickets--;}return NULL;
}int main() { // 主线程// 创建3个子线程pthread_t tid1, tid2, tid3;pthread_create(tid1, NULL, sellticket, NULL);pthread_create(tid2, NULL, sellticket, NULL);pthread_create(tid3, NULL, sellticket, NULL);// 回收子线程的资源,阻塞pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);// 或设置线程分离子线程结束后资源自动回收// pthread_detach(tid1);// pthread_detach(tid2);// pthread_detach(tid3);pthread_exit(NULL); // 退出主线程return 0;// 如果不设置上一步主线程结束后子线程也会全结束。
}3.2.1 互斥量/锁 为避免线程更新共享变量时出现问题可以使用 互斥量 mutex 是 mutual exclusion 的缩写来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的 原子访问。 互斥量有两种状态 已锁定 locked 和 未锁定 ( unlocked 。任何时候至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁将可能 阻塞线程 或者 报错失败具体取决于加锁时使用的方法。 一旦线程锁定互斥量随即成为该互斥量的所有者只有所有者才能给互斥量解锁。一般情况下对每一共享资源可能由多个相关变量组成会使用不同的互斥量每一线程在访问同一资源时将采用如下协议 针对共享资源 锁定互斥量访问 共享资源对互斥量 解锁 如果多个线程试图执行这一块代码一个临界区事实上只有一个线程能够持有该 互斥量其他线程将遭到阻塞即同时只有一个线程能够进入这段代码区域如下图所示 互斥量相关操作函数 互斥量的类型 pthread_mutex_t // 初始化互斥量
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);int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 功能初始化互斥量 参数 mutex 需要初始化的互斥量变量attr 互斥量相关的属性NULL restrict : C语言的修饰符被修饰的指针不能由另外的一个指针进行操作。 pthread_mutex_t *restrict mutex xxx;
pthread_mutex_t *mutex1 mutex; // 错误解决上面买票的问题 #include stdio.h
#include pthread.h
#include unistd.h// 全局变量所有的线程都共享这一份资源。
int tickets 1000;// 创建一个互斥量
pthread_mutex_t mutex;void * sellticket(void * arg) {// 卖票while(1) {// 加锁pthread_mutex_lock(mutex);if(tickets 0) {usleep(6000);printf(%ld 正在卖第 %d 张门票\n, pthread_self(), tickets);tickets--;}else {// 解锁pthread_mutex_unlock(mutex);break;}// 解锁pthread_mutex_unlock(mutex);}return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(mutex, NULL);// 创建3个子线程pthread_t tid1, tid2, tid3;pthread_create(tid1, NULL, sellticket, NULL);pthread_create(tid2, NULL, sellticket, NULL);pthread_create(tid3, NULL, sellticket, NULL);// 回收子线程的资源,阻塞pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);pthread_exit(NULL); // 退出主线程// 释放互斥量资源pthread_mutex_destroy(mutex);return 0;
}3.2.2 死锁 有时一个线程需要同时访问两个或更多不同的共享资源而每个资源又都由不同的互斥量管理。当超过一个线程 加锁 同一组互斥量 时就有可能发生 死锁。 两个或两个以上的进程在执行过程中因争夺共享资源而造成的一种 互相等待 的现象若无外力作用它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。 死锁的几种场景 忘记 释放锁重复 加锁多线程多锁抢占锁资源 #include stdio.h
#include pthread.h
#include unistd.h// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;void * workA(void * arg) {pthread_mutex_lock(mutex1);sleep(1);pthread_mutex_lock(mutex2);printf(workA....\n);pthread_mutex_unlock(mutex2);pthread_mutex_unlock(mutex1);return NULL;
}void * workB(void * arg) {pthread_mutex_lock(mutex2);sleep(1);pthread_mutex_lock(mutex1);printf(workB....\n);pthread_mutex_unlock(mutex1);pthread_mutex_unlock(mutex2);return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(mutex1, NULL);pthread_mutex_init(mutex2, NULL);// 创建2个子线程pthread_t tid1, tid2;pthread_create(tid1, NULL, workA, NULL);pthread_create(tid2, NULL, workB, NULL);// 回收子线程资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 释放互斥量资源pthread_mutex_destroy(mutex1);pthread_mutex_destroy(mutex2);return 0;
}3.2.3 读写锁 当有一个线程已经持有 互斥锁 时互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形当前持有互斥锁的线程只是要 读访问 共享资源而同时有其它几个线程也想 读取 这个共享资源但是由于互斥锁的排它性所有其它线程都无法获取锁也就无法读访问共享资源了但是实际上多个线程同时 读访问 共享资源并不会导致问题。 在对数据的读写操作中更多的是 读操作写操作较少例如对数据库数据的读写应用。为了满足当前能够允许多个读出但只允许一个写入的需求线程提供了 读写锁 来实现。 读写锁 的特点 如果有其它线程读数据则允许其它线程执行读操作但不允许写操作。如果有其它线程写数据则其它线程都不允许读、写操作。写是独占的写的优先级高。 读写锁相关操作函数 读写锁 的类型 pthread_rwlock_t // 初始化
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_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 尝试加写锁// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);案例8个线程 操作 同一个全局变量。
3个线程 不定时 写 这个全局变量5个线程 不定时的 读 这个全局变量
#include stdio.h
#include pthread.h
#include unistd.h// 创建一个共享数据
int num 1;
// pthread_mutex_t mutex; // 定义互斥锁
pthread_rwlock_t rwlock; // 定义读写锁void * writeNum(void * arg) {while(1) {pthread_rwlock_wrlock(rwlock);num;printf(write, tid : %ld, num : %d\n, pthread_self(), num);pthread_rwlock_unlock(rwlock);usleep(100);}return NULL;
}void * readNum(void * arg) {while(1) {pthread_rwlock_rdlock(rwlock);printf(read, tid : %ld, num : %d\n, pthread_self(), num);pthread_rwlock_unlock(rwlock);usleep(100);}return NULL;
}int main() {pthread_rwlock_init(rwlock, NULL);// 创建3个写线程5个读线程pthread_t wtids[3], rtids[5];for(int i 0; i 3; i) {pthread_create(wtids[i], NULL, writeNum, NULL);}for(int i 0; i 5; i) {pthread_create(rtids[i], NULL, readNum, NULL);}// 设置线程分离for(int i 0; i 3; i) {pthread_detach(wtids[i]);}for(int i 0; i 5; i) {pthread_detach(rtids[i]);}pthread_exit(NULL); // 主线程退出子线程继续运行pthread_rwlock_destroy(rwlock);return 0;
}3.3 生产者消费者模型
生产者消费者模型中的对象 生产者多个消费者多个容器 当容器中已满生产者停止生产并通知消费者消费产品 当容器中为空消费者停止消费并通知生产者生产产品。 生产者消费者模型粗略的版本先不考虑容器的容量 #include stdio.h
#include pthread.h
#include stdlib.h
#include unistd.h// 创建一个互斥量
pthread_mutex_t mutex;struct Node{ // 链表节点int num;struct Node *next;
};// 头结点
struct Node * head NULL;void * producer(void * arg) { // 生产者// 不断的创建新的节点添加到链表中while(1) {pthread_mutex_lock(mutex);struct Node * newNode (struct Node *)malloc(sizeof(struct Node));newNode-next head;head newNode;newNode-num rand() % 1000; // 随机数printf(add node, num : %d, tid : %ld\n, newNode-num, pthread_self());pthread_mutex_unlock(mutex);usleep(100);}return NULL;
}void * customer(void * arg) { // 消费者while(1) {pthread_mutex_lock(mutex);// 保存头结点的指针struct Node * tmp head;// 判断是否有数据if(head ! NULL) {// 有数据head head-next;printf(del node, num : %d, tid : %ld\n, tmp-num, pthread_self());free(tmp);pthread_mutex_unlock(mutex);usleep(100);} else {// 没有数据pthread_mutex_unlock(mutex);}}return NULL;
}int main() {pthread_mutex_init(mutex, NULL);// 创建5个生产者线程和5个消费者线程pthread_t ptids[5], ctids[5];for(int i 0; i 5; i) {pthread_create(ptids[i], NULL, producer, NULL);pthread_create(ctids[i], NULL, customer, NULL);}for(int i 0; i 5; i) { // 线程分离pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1) {sleep(10);}pthread_mutex_destroy(mutex); // 线程退出退出主线程pthread_exit(NULL);return 0;
}存在的问题当没有数据的时候消费者 没有去通知 生产者生产从而一直在循环加锁解锁浪费了资源。
3.3.1 条件变量
条件变量 的类型 pthread_cond_t
条件变量 不是锁某个条件满足以后可以 引起 / 解除 线程阻塞。
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_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_timedwait(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 stdio.h
#include pthread.h
#include stdlib.h
#include unistd.h// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;struct Node{int num;struct Node *next;
};// 头结点
struct Node * head NULL;void * producer(void * arg) {// 不断的创建新的节点添加到链表中while(1) {pthread_mutex_lock(mutex);struct Node * newNode (struct Node *)malloc(sizeof(struct Node));newNode-next head;head newNode;newNode-num rand() % 1000;printf(add node, num : %d, tid : %ld\n, newNode-num, pthread_self());// 只要生产了一个就通知消费者消费pthread_cond_signal(cond);pthread_mutex_unlock(mutex);usleep(100);}return NULL;
}void * customer(void * arg) {while(1) {pthread_mutex_lock(mutex);// 保存头结点的指针struct Node * tmp head;// 判断是否有数据if(head ! NULL) {// 有数据head head-next;printf(del node, num : %d, tid : %ld\n, tmp-num, pthread_self());free(tmp);pthread_mutex_unlock(mutex);usleep(100);} else {// 没有数据需要等待// 当这个函数调用阻塞的时候会对互斥锁进行解锁当不阻塞的继续向下执行会重新加锁。pthread_cond_wait(cond, mutex); pthread_mutex_unlock(mutex); }}return NULL;
}int main() {pthread_mutex_init(mutex, NULL);pthread_cond_init(cond, NULL);// 创建5个生产者线程和5个消费者线程pthread_t ptids[5], ctids[5];for(int i 0; i 5; i) {pthread_create(ptids[i], NULL, producer, NULL);pthread_create(ctids[i], NULL, customer, NULL);}for(int i 0; i 5; i) {pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1) {sleep(10);}pthread_mutex_destroy(mutex);pthread_cond_destroy(cond);pthread_exit(NULL);return 0;
}3.3.2 信号量/灯 和条件变量类似信号量的类型 sem_t 灯亮资源可用每个产品可以比作一个灯灯灭资源不可用 // 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 释放资源
int sem_destroy(sem_t *sem);// 对信号量加锁调用一次对信号量的值-1如果值为0就阻塞。不是立马阻塞
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem); // 尝试阻塞如果值为0就阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 如果为0, 阻塞这么长abs_timeout时间// 对信号量解锁调用一次对信号量的值1
int sem_post(sem_t *sem);
// 获取值
int sem_getvalue(sem_t *sem, int *sval);int sem_init(sem_t *sem, int pshared, unsigned int value); 初始化信号量参数 sem : 信号量变量的地址pshared : 0 用在 线程 间 非0 用在 进程 间value : 信号量中的值 查看说明文档man sem_init 在开发程序时一般采用 多线程开发因为多线程比较 轻量。 /* 伪代码sem_t psem; // 生产者的信号量sem_t csem; // 消费者的信号量init(psem, 0, 8);init(csem, 0, 0);producer() {sem_wait(psem); // -1sem_post(csem); // 1通知消费者可以消费一个了}customer() {sem_wait(csem); // -1sem_post(psem); // 1通知生产者可以生产一个了}*/#include stdio.h
#include pthread.h
#include stdlib.h
#include unistd.h
#include semaphore.h// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem; // 生产者的
sem_t csem; // 消费者的struct Node{int num;struct Node *next;
};// 头结点
struct Node * head NULL;void * producer(void * arg) {// 不断的创建新的节点添加到链表中while(1) {sem_wait(psem);pthread_mutex_lock(mutex);struct Node * newNode (struct Node *)malloc(sizeof(struct Node));newNode-next head;head newNode;newNode-num rand() % 1000;printf(add node, num : %d, tid : %ld\n, newNode-num, pthread_self());pthread_mutex_unlock(mutex);sem_post(csem);}return NULL;
}void * customer(void * arg) {while(1) {sem_wait(csem);pthread_mutex_lock(mutex);// 保存头结点的指针struct Node * tmp head;head head-next;printf(del node, num : %d, tid : %ld\n, tmp-num, pthread_self());free(tmp);pthread_mutex_unlock(mutex);sem_post(psem);}return NULL;
}int main() {pthread_mutex_init(mutex, NULL);sem_init(psem, 0, 8);sem_init(csem, 0, 0);// 创建5个生产者线程和5个消费者线程pthread_t ptids[5], ctids[5];for(int i 0; i 5; i) {pthread_create(ptids[i], NULL, producer, NULL);pthread_create(ctids[i], NULL, customer, NULL);}for(int i 0; i 5; i) {pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1) {sleep(10);}pthread_mutex_destroy(mutex);pthread_exit(NULL);return 0;
}