长宁区网站建设公,工商营业执照网上查询官网,电子商务网站建设品牌,网站开发与网页制作的区别传送门#xff1a;Linux多线程编程实例解析 . linux多线程编程——同步与互斥 . 传统多任务操作系统中一个可以独立调度的任务#xff08;或称之为顺序执行流#xff09;是一个进程。每个程序加载到内存后只可以唯一地对应创建一个顺序执行流#xff0c;即传统意义的进程。… 传送门Linux多线程编程实例解析 . linux多线程编程——同步与互斥 . 传统多任务操作系统中一个可以独立调度的任务或称之为顺序执行流是一个进程。每个程序加载到内存后只可以唯一地对应创建一个顺序执行流即传统意义的进程。每个进程的全部系统资源是私有的如虚拟地址空间文件描述符和信号处理等等。使用多进程实现多任务应用时存在如下问题 1任务切换即进程间上下文切换系统开销比较大。虚拟地址空间以及task_struct 都需要切换 2多任务之间的协作比较麻烦涉及进程间通讯。因为不同的进程工作在不同的地址空间 所以为了提高系统的性能许多操作系统规范里引入了轻量级进程的概念也被称为线程。 一、线程基础 通常线程指的是共享相同地址空间的多个任务。线程最大的特点就是在同一个进程中创建的线程共享该进程的地址空间但一个线程仍用task_struct 来描述线程和进程都参与统一的调度。所以多线程的好处便体现出来 1大大提高了任务切换的效率因为各线程共享进程的地址空间任务切换时只要切换task_struct 即可 2线程间通信比较方便因为在同一块地址空间数据共享 当然共享地址空间也会成为线程的缺点因为共享地址空间如果其中一个线程出现错误比如段错误整个线程组都会崩掉 Linux之所以称呼其线程为LWP( Light Weight Process )因为从内核实现的角度来说它并没有为线程单独创建一个结构而是继承了很多进程的设计 1继承了进程的结构体定义task_struct 2没有专门定义线程ID复用了PID 3更没有为线程定义特别的调度算法而是沿用了原来对task_struct 的调度算法。 在最新的Linux内核里线程已经替代原来的进程称为调度的实际最小单位。 原来的进程概念可以看成是多个线程的容器称之为线程组即一个进程就是所有相关的线程构成的一个线程组。传统的进程等价于单线程进程。 每个线程组都有自己的标识符 tgid (数据类型为 pid_t )其值等于该进程(线程组)中的第一个线程(group_leader)的PID。 1、创建线程 pthread_create()函数描述如下 所需头文件#include pthread.h函数原型 int pthread_create(pthread_t *thread,const pthread_attr_t *attr, void *(* routine)(void *), void *arg) 函数参数 thread 创建的线程 attr 指定线程的属性NULL表示使用缺省属性 routine 线程执行的函数 arg 传递给线程执行的函数的参数 函数返回值 成功 0 出错 -1 1这里routine 是回调函数callback其函数类型由内核来决定这里我们将其地址传给内核这个函数并不是线程创建了就会执行而是只有当其被调度到cpu上时才会被执行具体回调函数的讲解移步Linux C 函数指针应用---回调函数 . 2arg 是线程执行函数的参数这里我们将其地址穿进去使用时需要先进行类型转换才能使用如果参数不止一个我们可以将其放入到结构体中 2、pthread_join () 函数 其函数描述如下 所需头文件#include pthread.h函数原型int thread_join(pthread_t thread, void ** value_ptr)函数参数 thread 要等待的线程 value_ptr 指针 *value_ptr 指向线程返回的参数 函数返回值 成功 0 出错 -1 这里我们可以看到 value_ptr 是个二级指针其是出参存放的是线程返回参数的地址 3、pthread_exit 函数 其函数描述如下 所需头文件#include pthread.h函数原型int pthread_exit(void *value_ptr)函数参数value_ptr 线程退出时返回的值函数返回值 成功0 出错-1 和进程中的exit() 、wait()一样这里pthread_join 与 pthread_exit 是工作在两个线程之中 下面看一个实例 [cpp] view plaincopy #include stdio.h #include stdlib.h #include string.h #include pthread.h char message[32] Hello World!; void *thread_function(void *arg); int main() { pthread_t a_thread; void *thread_result; if(pthread_create(a_thread,NULL,thread_function,(void *)message) 0) { perror(fail to pthread_create); exit(-1); } printf(waiting for thread to finish\n); if(pthread_join(a_thread,thread_result) 0) { perror(fail to pthread_join); exit(-1); } printf(Message is now %s\n,message); printf(thread_result is %s\n,(char *)thread_result); return 0; } void *thread_function(void *arg) { printf(thread_function is running,argument is %s\n,(char *)arg); strcpy(message,marked by thread); pthread_exit(Thank you for the cpu time); } 编译 [cpp] view plaincopy fsubuntu:~/qiang/thread/0107$ gcc -o thread thread.c -lpthread fsubuntu:~/qiang/thread/0107$ 线程通过第三方的线程库来实现所以这里要 -lpthread -l 是链接一个库这个库是pthread 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/thread/0107$ ./thread waiting for thread to finish thread_function is running,argument is Hello World! Message is now marked by thread thread_result is Thank you for the cpu time fsubuntu:~/qiang/thread/0107$ 从这个程序我们可以看到线程之间是如何通信的线程之间通过二级指针来传送参数的地址这是进程所不具备的因为他们的地址空间独立但两个线程之间的通信传递的数据的生命周期必须是静态的。可以使全局变量、static修饰的数据、堆里面的数据这个程序中的message就是一个全局变量。其中一个线程可以修改它另一个线程得到它修改过后的message。 二、线程的同步和互斥 先来了解同步和互斥的基本概念 临界资源某些资源来说其在同一时间只能被一段机器指令序列所占用。这些一次只能被一段指令序列所占用的资源就是所谓的临界资源。 临界区对于临界资源的访问必须是互斥进行。也就是当临界资源被一个指令序列占用时另一个需要访问相同临界资源的指令序列就不能被执行。指令序列不能执行的实际意思就是其所在的进程/线程会被阻塞。所以我们定义程序内访问临界资源的代码序列被称为临界区。 互斥是指同事只允许一个访问者对临界资源进行访问具有唯一性和排它性。但互斥无法限制访问这个对资源的访问顺序即访问时无序的。 同步是指在互斥的基础上通过其他机制实现访问者对资源的有序访问。 1、线程间互斥 引入互斥(mutual exlusion)锁的目的是用来保证共享数据的完整性。 互斥锁主要用来保护临界资源。每个临界资源都有一个互斥锁来保护任何时刻最多只能有一个线程能访问该资源线程必须先获得互斥锁才能访问临界资源访问完资源后释放该锁。如果无法获得锁线程会阻塞直到获得锁为止 通常我们在临界区前上锁临界区后解锁 1初始化互斥锁函数 所需头文件#include pthread.h函数原型 int pthread_mutex_init (pthread_mutex_t *mutex, pthread_mutexattr_t *attr ) //初始化互斥锁 函数参数 mutex互斥锁 attr 互斥锁属性 // NULL表示缺省属性 函数返回值 成功0 出错-1 2申请互斥锁函数 所需头文件#include pthread.h函数原型 int pthread_mutex_lock(pthread_mutex_t *mutex) //申请互斥锁 函数参数 mutex互斥锁 函数返回值 成功0 出错-1 3释放互斥锁函数 所需头文件#include pthread.h函数原型 int pthread_mutex_unlock(pthread_mutex_t *mutex) //释放互斥锁 函数参数 mutex互斥锁 函数返回值 成功0 出错-1 下面是一个实例 [cpp] view plaincopy #include stdio.h #include stdlib.h #include string.h #include pthread.h #include unistd.h //#define _LOCK_ unsigned int value1,value2,count; pthread_mutex_t mutex; void *function(void *arg); int main() { pthread_t a_thread; if(pthread_mutex_init(mutex,NULL) 0) { perror(fail to mutex_init); exit(-1); } if(pthread_create(a_thread,NULL,function,NULL) ! 0) { perror(fail to pthread_create); exit(-1); } while(1) { count; #ifdef _LOCK_ pthread_mutex_lock(mutex); #endif value1 count; value2 count; #ifdef _LOCK_ pthread_mutex_unlock(mutex); #endif } return 0; } void *function(void *arg) { while(1) { #ifdef _LOCK_ pthread_mutex_lock(mutex); #endif if(value1 ! value2) { printf(count %d,value1 %d,value2 %d\n,count,value1,value2); usleep(100000); } #ifdef _LOCK_ pthread_mutex_unlock(mutex); #endif } return NULL; } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/thread/0107$ ./mutex count 3368408,value1 3368408,value2 3368407 count 44174760,value1 44174760,value2 44174759 count 69313865,value1 69313865,value2 69313864 count 139035309,value1 139035309,value2 139035308 count 168803956,value1 168803956,value2 168803955 count 192992611,value1 192992611,value2 192992610 count 224279903,value1 224279903,value2 224279902 count 259586793,value1 259586793,value2 259586792 count 282057307,value1 282057307,value2 282057306 count 321607823,value1 321607823,value2 321607822 count 351629940,value1 351629940,value2 351629939 count 374130545,value1 374130545,value2 374130544 count 400727525,value1 400727525,value2 400727524 count 440219988,value1 440219988,value2 440219987 count 466069865,value1 466069865,value2 466069864 count 500581241,value1 500581241,value2 500581240 count 522649671,value1 522649671,value2 522649670 count 569234325,value1 569234325,value2 569234324 count 608139152,value1 608139152,value2 608139151 count 639493957,value1 639493957,value2 639493956 ..... 我们可以看到数据是不断被打印的说明 a 线程是可以访问临界资源的。 我们把#define _LOCK_前面的注释去掉这时就加上了互斥锁执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/thread/0107$ ./mutex 此时并没有数据被打印说明此时a线程中 value1 与 value 2 一直是相等的说明主线程执行是a线程并无法访问临界资源的。 2、线程间同步 同步(synchronization) 指的是多个任务线程按照约定的顺序相互配合完成一件事情 线程间同步——P / V 操作 信号量代表某一类资源其值表示系统中该资源当前可用的数量。 信号量是一个受保护的变量只能通过三种操作来访问 1初始化 2P操作申请资源 3V操作释放资源PS含义如下 [cpp] view plaincopy if (信号量的值大于0) { 请资源的任务继续运行 信号量的值 减一 } else { 请资源的任务阻塞 } VS含义如下 [cpp] view plaincopy if (没有任务在等待该资源) { 信号量的值 加一 } else { 唤醒第一个等待的任务让其继续运行 } 1、信号量初始化函数 所需头文件#include semaphore.h函数原型 int sem_int (sem_t *sem,int pshared,unsigned int value) //初始化信号量 函数参数 sem初始化的信号量 pshared信号量共享的范围0线程间使用 非0 进程间使用 value 信号量初值 函数返回值 成功0 出错-1 2P操作 所需头文件#include semaphore.h函数原型 int sem_wait (sem_t *sem) //P操作 函数参数 sem信号量 函数返回值 成功0 出错-1 3V操作 所需头文件#include semaphore.h函数原型 int sem_post(sem_t *sem) //V操作 函数参数 sem信号量 函数返回值 成功0 出错-1 下面是个实例 [cpp] view plaincopy #include stdio.h #include stdlib.h #include string.h #include pthread.h #include semaphore.h char buf[60]; sem_t sem; void *function(void *arg); int main(int argc, char *argv[]) { pthread_t a_thread; void *thread_result; if(sem_init(sem,0,0) ! 0) { perror(fail to sem_init); exit(-1); } if(pthread_create(a_thread,NULL,function,NULL) ! 0) { perror(fail to pthread_create); exit(-1); } printf(input quit to exit\n); do { fgets(buf,60,stdin); sem_post(sem); } while(strncmp(buf,quit,4) ! 0); return 0; } void *function(void *arg) { while(1) { sem_wait(sem); printf(you enter %d characters\n,strlen(buf) - 1); } } 执行结果如下 [cpp] view plaincopy fsubuntu:~/qiang/thread/0107$ ./sem input quit to exit xiao you enter 4 characters zhi you enter 3 characters qiang you enter 5 characters quit fsubuntu:~/qiang/thread/0107$ 我们可以看到两个线程是同步的。