当前位置: 首页 > news >正文

建设培训考试服务网站做爰网站下载

建设培训考试服务网站,做爰网站下载,建设电影网站广告哪里找,怎么使用wordpress里的主题目录 进程互斥 计算 - 时序问题 加锁保护 pthread_mutex_lock pthread_mutex_unlock 使用init与destory pthread_mutex_init phtread_mutex_destory 锁的实现原理 图 可重入VS线程安全 死锁 Linux线程同步 条件变量 系统调用 进程互斥 进程线程间的互斥相关…目录 进程互斥 计算 - 时序问题 加锁保护 pthread_mutex_lock pthread_mutex_unlock 使用init与destory pthread_mutex_init phtread_mutex_destory 锁的实现原理 图 可重入VS线程安全 死锁 Linux线程同步 条件变量 系统调用 进程互斥 进程线程间的互斥相关背景概念 临界资源多线程执行流共享的资源在任何时刻只能被一个执行流访问的资源就叫做临界资源。一个资源被多执行流共享的情况下通过一定的方法让任何时刻只能被一个执行流访问的资源临界区每个线程内部访问临界资源的代码就叫做临界区。没有访问临界资源的代码叫做正常代码互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用。原子性在任意时刻不会因为某些原因事物只做一半要么不做要么就做全。即该操作只有两态要么完成要么未完成。 需要互斥的场景 大部分情况线程使用的数据都是局部变量变量的地址空间在线程栈空间内这种情况变量归属单个线程其他线程无法获得这种变量。 但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通过数据的共享完成线程之间的交互 —— 此时因为其中的一些特性数据不一致就会使用到互斥。 #include iostream #include cstdio #include pthread.h #include unistd.hint tickets 1000;void *getTickets(void *args) {(void)args; // 防止release下因为未使用而报错while(true){if(tickets 0){usleep(1000);printf(%p: %d\n, pthread_self(), tickets);tickets--;}elsebreak;}return nullptr; }int main() {pthread_t t1,t2,t3;// 多线程抢票的逻辑 - t1、t2、t3对getTickets函数重入pthread_create(t1, nullptr, getTickets, nullptr);pthread_create(t2, nullptr, getTickets, nullptr);pthread_create(t3, nullptr, getTickets, nullptr);// 线程等待pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr); } (不)可重入函数         main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是main函数和sighandler先后向链表中插入两个节点而最后只有一个节点真正插入链表中了。于是便出现了经典的内存泄漏问题。此问题存在且非常不容易排查。         排查代码的时候可以发现我们的代码写的没有任何问题。对应的main函数、单链表头插insert、信号捕捉sighandler、函数调用都没有问题 —— 这个问题的产生严格来说并不是代码问题而是因为操作系统调度导致的进程时序的变化 —— 时序问题。         像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数反之 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。 可重入函数 VS 不可重入函数         是函数的一种特征目前我们用的90%函数都是不可重入的。 不可重入函数好编写。可重入函数不好编写书写成本高。如果一个函数符合以下条件之一则是不可重入的:   调用了new、malloc或free因为new、malloc也是用全局链表来管理堆的。99%的STL容器都是不可重入的。函数里面带static的。调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。可重入函数需要保证函数其是独立的没有访问任何的全局数据。 此时我们会发现一个问题票居然减到负数了。所以 —— 在多线程的抢票下对于共享资源如果不加以保护就可能在一定的时序下引起问题。 计算 - 时序问题 代码经过编译变成二进制可执行程序。加载之后运行其一定在内存里代码和数据都在内存里。而计算需要CUP进行计算所以计算的第一种事情就是将内存当中的数据加载入CPU当中 —— 线程读取内存中的数据读到CPU的寄存器里 —— 把数据读到当前执行流的上下文当中。 判断的本质也是一种计算 计算分类 数值计算逻辑计算从切换讲 当一个线程刚刚将这个判断加载到寄存器里就被切换了。其他线程也过来将这个判断加载到寄存器里。此时如果判断就是true就会都是读到true。于是多个执行流进入循环进行抢票多个线程减票数于是导致票成负数。 从并行讲 多个线程并行运行可能多个线程同时判断true进入循环进行抢票。于是多个执行流进入循环进行抢票多个线程减票数于是导致票成负数。 Note         因为由于并发的问题进而导致对应的数据变成负数。 同样的道理对全局数据的 -- 操作也会有问题。  因为 ticket-- 不是原子性的 可以利用objdump -d命令取出ticket--部分的汇编代码objdump -d a.out test.objdump 400511:    8b 05 15 0b 20 00        mov    0x200b15(%rip),%eax        # 60102c ticket 400517:    83 e8 01                 sub    $0x1,%eax 40051a:    89 05 0c 0b 20 00        mov    %eax,0x200b0c(%rip)        # 60102c ticket 引发了数据紊乱的问题造成了数据不一致的问题。这就是因为线程不断切换而对一个不加保护的全局变量做切换可能会引发的问题。 此处问题tickets的票数为负数的问题就是二者共同导致的于是我们要避免这样问题的产生 —— 多执行流对这类问题的数据进行保护 —— 加锁保护。 加锁保护 写代码的过程中使用的最高频的保护策略互斥锁 锁的使用原理 代码必须要有互斥行为当代码进入临界区执行时不允许其他线程进入该临界区。 如果多个线程同时要求执行临界区的代码并且临界区没有线程在执行那么只能允许一个线程进入该临界区。 如果线程不在临界区中执行那么该线程不能阻止其他线程进入临界区。 首先需要我们自己定义一把锁 随后需要我们将锁进行初始化  1. 对于局部的锁初始化利用pthread_mutex_init。也可以对全局的使用 较为难后面讲解 2. 对这把锁只要是我们定义的是静态或全局我们可以直接使用PTHREAD_MUTEX_INITIALIZER宏进行初始化。 有了定义的这一把锁并初始化之后。就可以直接使用这一把锁来进行对特定的区域进行保护。 于是这把锁用来保护全局变量tickets称作为对临界资源进行访问时对其进行保护。即对其中访问临界资源的临界区进行加锁保护。对临界区加锁 —— pthread_mutex_lock。 pthread_mutex_lock #include pthread.h // 加锁 int pthread_mutex_lock(pthread_mutex_t *mutex); 参数         mutex传入我们定义并初始化的一把锁。 返回值 成功返回0。失败返回错误码。pthread_mutex_lock—— 申请锁的时候别的线程也在申请如果申请锁不成功会直接阻塞。 pthread_mutex_trylock —— 申请锁的时候别的线程也在申请如果申请锁不成功会直接返回。 意义凸显于死锁在死锁部分讲解 使用锁对特定的区域进行保护 所有的线程进入循环抢票时都需要先执行语句 pthread_mutex_lock(mtx); 。于是由于这个锁的特点任何一个时刻只允许一个成功的线程成功的获取这把锁然后继续向后执行。其他的线程只能默认的阻塞等待直到拿到锁的线程最终将锁释放掉才能有机会进入执行 —— 这就是互斥概念。 可以理解为         加锁  ——  互斥  ——  串行执行。 有加锁操作就有解锁操作 —— pthread_mutex_unlock。 pthread_mutex_unlock #include pthread.h // 解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); 参数         mutex传入我们定义并初始化的一把锁。 返回值 成功返回0。失败返回错误码。注意一定要解锁因为锁是全局的就算具有锁的进程退出了终止了。但是全局的锁还是处于被修改的状态也就是处于加锁状态其他线程就无法继续向后执行。 此处容易出现一个错误 加锁pthread_mutex_lock与解锁pthread_mutex_unlock之间的代码就称作临界区。被串行化访问的共享的全局的tickets就叫做临界资源。以前是多线程并发的执行也就是彼此之间的数据层面会影响但是执行之间是各自跑各自的不会互相干扰。但是当加锁之后就会引起执行流之间互相影响只能一个执行流跑。这一定的也就降低了效率并且会导致一个新的几乎无法解决的问题。 #include iostream #include cstdio #include pthread.h #include unistd.hint tickets 1000;// 加锁保护 pthread_mutex_t mtx PTHREAD_MUTEX_INITIALIZER; // pthread_mutex_t 就是原生数据库提供的一个数据类型void *getTickets(void *args) {(void)args; // 防止release下因为未使用而报错while (true){pthread_mutex_lock(mtx);if (tickets 0) // 1.判断的本质也是计算的一种{usleep(1000);printf(%p: %d\n, pthread_self(), tickets);tickets--; // 2.也可能出现问题pthread_mutex_unlock(mtx);}else{pthread_mutex_unlock(mtx);break;}}return nullptr; }int main() {pthread_t t1, t2, t3;// 多线程抢票的逻辑 - t1、t2、t3对getTickets函数重入pthread_create(t1, nullptr, getTickets, nullptr);pthread_create(t2, nullptr, getTickets, nullptr);pthread_create(t3, nullptr, getTickets, nullptr);// 线程等待pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr); }补充         此处不用当一回事只是一个理解见识真正的知识属于线程同步的概念后面讲解 为什么打印线程发现一段时间内都是一个线程         因为在抢票的时候因为每个线程都有自己的时间片。一个线程在执行抢票期间其他的线程在进行等待执行的线程抢完票就解锁了。可是有可能此线程优先级很高进而又得到锁继续抢票。         主要原因所有的新线程执行的内容一样导致调度器内部出现此问题。可以让每一个线程执行不同的任务改变。 #include iostream #include cstdio #include cstdlib #include ctime #include pthread.h #include unistd.hint tickets 1000;// 加锁保护 pthread_mutex_t mtx PTHREAD_MUTEX_INITIALIZER; // pthread_mutex_t 就是原生数据库提供的一个数据类型void *getTickets(void *args) {(void)args; // 防止release下因为未使用而报错while (true){pthread_mutex_lock(mtx);if (tickets 0) // 1.判断的本质也是计算的一种{usleep(1000);printf(%s: %d\n, (void*)args, tickets);tickets--; // 2.也可能出现问题pthread_mutex_unlock(mtx);}else{pthread_mutex_unlock(mtx);break;}usleep(rand()%10000); // 模拟每一个线程的任务不同}return nullptr; }int main() {srand((unsigned long)time(nullptr));pthread_t t1, t2, t3;// 多线程抢票的逻辑 - t1、t2、t3对getTickets函数重入pthread_create(t1, nullptr, getTickets, (void*)thread one);pthread_create(t2, nullptr, getTickets, (void*)thread two);pthread_create(t3, nullptr, getTickets, (void*)thread three);// 线程等待pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr); } Note         加锁保护加锁的时候一定要保证加锁的粒度越小越好。不要将无关竟要的代码也放入。 使用init与destory pthread_mutex_init #include pthread.h //对我们定义的锁进行初始化。 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);参数         mutex传入我们定义的一把锁。         attr锁的属性 —— 可以不管直接设为nullptr。 返回值 成功返回0。失败返回错误码。使用pthread_mutex_init初始化局部锁。 并且最后不要锁了就必须要用phtread_mutex_destory对锁进行释放。 phtread_mutex_destory #include pthread.h //销毁锁 int pthread_mutex_destory(pthread_mutex_t *mutex); 参数         mutex传入我们定义并初始化的一把锁。 返回值 成功返回0。失败返回错误码。销毁互斥量需要注意 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁。 不要销毁一个已经加锁的互斥量。 已经销毁的互斥量要确保后面不会有线程再尝试加锁。 需要注意的是我们在主线程内部创建的局部锁线程是看不到的所以我们需要将锁传递给每一个线程此处也是对pthread_create的第四个参数的升级利用此处用可以采取使用类的方式封装锁等传递进入。 #include iostream #include cstdio #include cstdlib #include ctime #include pthread.h #include unistd.hint tickets 1000;class ThreadData { public:ThreadData(const std::string n, pthread_mutex_t *pm):tname(n), pmtex(pm){}std::string tname;pthread_mutex_t *pmtex; };void *getTickets(void *args) {ThreadData *td (ThreadData*)args;while (true){pthread_mutex_lock(td-pmtex);if (tickets 0) // 1.判断的本质也是计算的一种{usleep(1000);printf(%s: %d\n, td-tname.c_str(), tickets);tickets--; // 2.也可能出现问题pthread_mutex_unlock(td-pmtex);}else{pthread_mutex_unlock(td-pmtex);break;}usleep(rand()%10000); // 模拟每一个线程的任务不同}delete td;return nullptr; }#define THREAD_NUM 3int main() {pthread_mutex_t mtx;pthread_mutex_init(mtx, nullptr);srand((unsigned long)time(nullptr));pthread_t tid[THREAD_NUM];for(int i 0; i THREAD_NUM; i){std::string name thread ;name std::to_string(i 1);ThreadData *td new ThreadData(name, mtx);pthread_create(tid i, nullptr, getTickets, (void*)td);}// 线程等待for(int i 0; i THREAD_NUM; i){pthread_join(tid[i], nullptr);}pthread_mutex_destroy(mtx);return 0; } #问加锁了之后线程在临界区中是否会切换 会切换因为是多条语句操作系统有切换的权利但是不会有问题。 使用层面上的理解 当持有锁的线程进入临界区的时候是随时随地可能被切换但是这与平时的不同。因为临界区中且换该线程一定抢到了锁并且没有释放锁。所以即便是该线程不再执行了但是其它线程想执行也必须具有锁但锁已经被切换的线程拿走了。 Note         一个线程不申请锁单纯的访问临界资源 -- 错误的编码方式编码规则 于是对于线程2来说对其有意义的事 线程1没有持有锁(什么都没做)此时我可以申请锁。线程1释放锁(做完)此时我可以申请锁。体现出线程1持有锁期间对于其他线程来说线程1的操作要么有锁、要么没锁就是原子性的。 #问加了锁就是串行吗 是的执行临界区代码一定事串行的。不然如何保证的原子性 #问要访问临界资源每一个线程都必须现申请锁每一个线程都必须先看到同一把锁并访问它锁本身是不是就是一种共享资源那锁由谁来保护 所以为了保证锁的安全申请和释放锁必须是原子性的。重点锁是如何实现的 锁的实现原理 在汇编的视角 只有一条汇编语句就认为该汇编语句的执行是原子的。于是为了实现互斥锁操作大多数体系结构都提供了swap或exchange指令以一条汇编的方式将内存和CPU内寄存区数据进行交换。 在执行流的视角 CPU内部的寄存器的本质当前执行流的上下文寄存器的空间是被所有的执行流共享的但是寄存器的内容上下文是被每一个执行流私有的。 lock的伪代码核心是交换并非拷贝 —— 交换 - 永远只有一个1。 前面的学习使得我们知道之所以出现数据不一致问题是因为一个线程将一个共享数据拿走了但是没来的及改变共享数据的数值就被切换成了新的线程来使用这个共享数据。 而在此是交换并非拷贝于是可以理解为有锁即为1没有锁即为0。于是利用交换的原理将共享数据的数值与线程的自身数值进行切换CUP中寄存器的数据本质上就是属于该线程的上下文数据。 lock只有执行了核心才会将锁1从共有数据交换为加锁线程的私有程序。私有数据没锁0交换为公共数据并且是只有一行的。所以一定是交换了 / 没交换两种状态。于是可以保证加锁的线程可以将锁带走因为加锁后锁就是该线程的上下文数据。 unlock与lock同理movb行执行就交换线程上下文数据与共享数据。所以保证了只要换锁一定释放没换一定还在改线程手上。 所以那锁由谁来保护 ——  锁自己利用一行汇编融汇贯通的理解         所以因为锁是原子性的利用一行汇编交换使得锁永远只有一个。并且对于拥有锁的线程来说锁是该线程的上下文是线程的私有资源除非unlock时交换回内存公共资源。不然永远为一个线程私有。也就进一步的说明了线程在临界区中被会切换是没有任何影响。 可重入VS线程安全 可重入针对于函数的说法一个函数被多个执行流重复进入的现象并且重复进入期间不会出现问题  ——  描述函数。线程安全在线程执行过程当中可能因为一个或若干个线程在执行的过程当中访问了某些不该访问的资源全局变量或者静态变量、共享的资源进而导致其他线程数据不一致问题、其他线程崩溃、进程终止  ——  描述线程。 常见的线程不安全的情况 不保护共享变量的函数。函数状态随着被调用状态发生变化的函数。返回指向静态变量指针的函数调用线程不安全函数的函数。 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般来说这些线程是安全的。类或者接口对于线程来说都是原子操作。通过加锁的方式局部上保持原子性。多个线程之间的切换不会导致该接口的执行结果存在二义性。函数不会有状态的变化 常见不可重入的情况 调用了malloc/free函数因为malloc函数是用全局链表来管理堆的。调用了标准I/O库函数标准I/O库的很多实现都以不可重入的方式使用全局数据结构。可重入函数体内使用了静态的数据结构。 常见可重入的情况 不使用全局变量或静态变量。不使用用malloc或者new开辟出的空间。不调用不可重入函数。不返回静态或全局数据所有数据都有函数的调用者提供。使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据。 void func() {int myerrno errno;// ……errno myerron; } 保证调用的时候调用之前与调用之后全局数据的errno的数据没有变化。因为在一个线程执行的时候另一个线程也执行可能会将errno的值进行修改。 可重入与线程安全联系 两个有交集但是是不同的概念 函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题。如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别 可重入函数是线程安全函数的一种。线程安全不一定是可重入的而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数若锁还未释放则会产生。死锁因此是不可重入的。 Note        线程安全有对错之分一定要写出线程安全的函数。         (不)可重入没有对错之分是特点。 死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 如在实际用锁的时候不一定只用了一把锁有可能用了2把锁以上。就可能出现以下问题 线程申请一把锁是原子的但是当申请完一把锁再去申请另一把锁的时候就可能会出现问题。 当两个线程同时执行线程A拿了锁1线程B拿了锁2。二者就要继续向后申请向后拿到各自的最后一把锁。 这个时候就出现了两个线程申请互相锁的状态 —— 死锁。 一把锁也可能出现死锁 #include iostream #include cstdio #include cstdlib #include pthread.h #include unistd.h// 加锁保护 pthread_mutex_t mtx PTHREAD_MUTEX_INITIALIZER;void *getTickets(void *args) {(void)args;pthread_mutex_lock(mtx);// ……//pthread_mutex_lock(mtx); 写错了将解锁写成了加锁pthread_mutex_lock(mtx);return nullptr; }int main() {srand((unsigned long)time(nullptr));pthread_t t1;pthread_create(t1, nullptr, getTickets, (void *)thread one);// 线程等待pthread_join(t1, nullptr);return 0; }         持有锁还将自己挂起永远不可能被唤醒。 死锁四个必要条件只要产生了死锁以下4个条件必定被满足—— 只要有效的解决其中至少一个问题就能解决死锁。互斥条件一个资源每次只能被一个执行流使用。请求与保持条件一个执行流因请求资源而阻塞时对已获得的资源保持不放。不剥夺条件一个执行流已获得的资源在末使用完之前不能强行剥夺。循环等待条件若干执行流之间形成一种头尾相接的循环等待资源的关系。 以故事的视角理解 锁 情侣关系 死锁 冷战 互斥条件即只能由一个女孩的存在于情侣关系中。请求与保持条件冷战女孩不认错想男孩认错。不剥夺条件冷战女孩不强制男孩认错。循环等待条件冷战男孩同时不认错想女孩认错。 避免死锁 破坏死锁的四个必要条件 加锁顺序一致 避免锁未释放的场景 资源一次性分配 互斥条件         是一切锁的最根本的功能而死锁是肯定使用了锁而导致的所以是不怎么考虑的解决方式。因为有情侣关系才会有冷战不可能说为了合好从而分手吧但也不得说避免冷战的出现就不谈恋爱是最有效的方式。         直观来说不适用锁也就是从根源解决死锁。 请求与保持条件         pthread_mutex_lock—— 申请锁的时候别的线程也在申请如果申请锁不成功会直接阻塞。         pthread_mutex_trylock —— 申请锁的时候别的线程也在申请如果申请锁不成功会直接返回。         中的tryloc作用就是凸显于此因为使用其申请锁的线程发现锁被其他线程占用并不会阻塞而是立即返回 在成功完成之后会返回零。其他任何返回值都表示出现了错误。EBUSY由于 mutex 所指向的互斥锁已锁定因此无法获取该互斥锁。        将返回值EBUSY的次数进行连续记录如果到到达一定数目该线程将自身所有的锁解掉。 不剥夺条件         线程尝试着去抢占竞争锁依据一定的条件我的优先级更高。强制的要求释放锁我来获取锁。 循环等待条件         通过编码的方式解决堆每一把锁进行编号要求每个线程申请锁时如果是申请多把锁每个线程都必须按照一定的顺序来申请。只能按照锁1锁2锁3……的申请不允许交叉申请。         尽量将资源一次性分配避免频繁加锁分配资源导致锁太多而锁被申请数量多导致易出现死锁的问题。 Linux线程同步 就如同我们前面所讲的抢票多线程抢票的时候可能会出现一段时间一直是一个线程抢票因数很多该线程因为优先级高疯狂的申请锁、释放锁。其也没有任何问题但是其是不合理 频繁申请到资源 —— 导致别的线程申请不到。太过于浪费进程自己的资源和临界区的资源。 频繁申请到资源 此没有错但是不合理的行为导致别的进程长时间的得不到资源而导致别的进程饥饿的问题。 就如同食堂排队。有一个人总是由于某些合理的原因一直处于队首并且打完饭又立马回到队首继续打饭甚至直到将饭打完。 所以 如果只有互斥就会导致所有的竞争情况为极强的随机性看调度器就有可能导致其他线程长时间得不到资源的情况。 太过于浪费进程自己的资源和临界区的资源 一个进程申请到资源首先需要做的是不是访问资源而是在申请锁之后先检测资源是否就绪如果就绪才会有后续操作。 就如同电影院买票票经过线程抢购售卖而前提是提供了票。于是忽票抢完之后需要电影院方再提供票售卖而此期间线程会持续的申请锁并检测防止有新票上线没有票的时候所有线程还在申请锁、检测、释放锁做无用功。         此期间线程在进行临界区提供检测从而浪费支援。 所以我们需要引入同步不是严格意义上的保证临界资源安全的方法主要为了解决访问临界资源合理性问题。 再以食堂打饭为例但是这一次极端一些         只有一个餐盘并且只有这一个餐盘才允许打饭多余要求没有。         于是有一个人早饭一过就开始等下午饭时间一到立马将餐盘一抢开始打饭但是这个人很奇怪每次打几粒米然后吃完将餐盘拿到厨房。然后厨房将餐盘刚刚洗完放回这人又饿了于是立马又拿起来继续跑去打饭按照规矩这个人也没错他离餐盘最近他肯定先抢的到甚至循环往复将饭打完了于是后面的人就吃不到饭菜。         于是便有了线程同步也就是管理员发现不对就加了一个规矩吃完的人不能立即再拿餐盘。后面等的人不能乱等必须按照前来时间排队。然后如果吃完放回餐盘想再打饭必须排在队尾。         意义让访问线程访问临界资源具有一定的顺序性。 条件变量 当我们申请临界资源前 - 先要对临界资源进行是否存在的检测 - 要做检测的本质也是访问临界资源。因为该线程不清楚资源是否被其他线程拿完大部分资源都是我们申请到锁但并不代表是有资源的。所以需要使用临界资源也就代表了需要要先对临界资源进行检测。 结论对临界资源的检测也一定是需要在加锁和解锁之间。 常规方式要检测条件就绪就注定了线程必须频繁的申请和释放锁使得线程处于一直在申请和释放锁这样会使得线程一直处于很忙的状态还不会创造什么价值。 #问有没有什么办法让线程检测资源处于不就绪的时候 1. 不要让线程在频繁检测 —— 让其处于等待。 2. 当条件就绪的时候通知对应的线程让他来进行资源的申请和访问。 条件变量当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。         例如一个线程访问队列时发现队列为空它只能等待直到其它线程将一个节点添加到队列中这种情况就需要用到条件变量。 同步概念与竞态条件同步在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题叫做同步。竞态条件因为时序问题而导致程序异常我们称之为竞态条件。系统调用 初始化与destory 1. 对于局部的条件变量初始化利用pthread_cond_init。也可以用于全局 #include pthread.h // 局部条件变量初始化 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);参数         cond要初始化的条件变量         attr局部变量的属性 —— 可以不管直接设为nullptr。 返回值 成功返回0。失败返回错误码。与锁的初始化相同需要pthread_cond_destroy。 #include pthread.h // 局部条件变量销毁 int pthread_cond_destroy(pthread_cond_t *cond); 参数         mutex传入我们定义并初始化的条件变量。 返回值 成功返回0。失败返回错误码。2. 对条件变量只要是我们定义的是静态或全局我们可以直接使用PTHREAD_COND_INITIALIZER宏进行初始化。 等待条件满足 现在我们需要的不是加锁和解锁我们需要的是在对应临界区中资源不就绪的时候不要频繁的检测等待即可pthread_cond_wait。 pthread_cond_wait #include pthread.h // 等待临界资源是否就绪 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);参数        cond要在这个条件变量上等待。         mutex互斥量锁 mutex就是对应着为了保护数据安全的一把锁也就是说除了要保证能够同步还要保持能够互斥。所以参数mutex与参数cond可以说是有渊源的  —— 使用条件变量必须有一把锁。为什么后面具体代码详细解释返回值 pthread_cond_timedwait 用的少此文不细致讲解 #include pthread.h // 等待临界资源是否就绪 int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);参数         前面两个参数与pthread_cond_wait一样。         restrict abstime传入的时间 也就是说等的时候是只能一直的等不是的我们可以使用pthread_cond_timedwait设置一个时间restrict abstime时间到了线程会自动醒来。返回值 唤醒等待 条件满足对应的线程就要收到通知也就是唤醒它。所以条件满足的时候需要向等待的线程发通知pthread_cond_broadcast、pthread_cond_signal。 pthread_cond_broadcast #include pthread.h // 可能不止一个线程在等资源就绪 —— 将对应等待的线程全部唤醒。 int pthread_cond_broadcast(pthread_cond_t *cond); 参数         cond当前在指定条件变量cond上阻塞所有线程。 返回值 成功返回0。失败返回错误码。pthread_cond_signal #include pthread.h // 唤醒指定的一个线程。 int pthread_cond_signal(pthread_cond_t *cond);参数         cond当前在指定条件变量cond上阻塞中的一个线程。 返回值 成功返回0。失败返回错误码。代码使用 下列代码从基础向上增加代码以做讲解 #先写一个简易的多线程程序 由于局部的锁与条件变量更难写所以此处演示局部的 #include iostream #include pthread.h #include string #include unistd.h#define NUMS 3 typedef void (*func_t)(const std::string );class ThreadDate { public:ThreadDate(const std::string name, func_t func) : name_(name), func_(func){}public:std::string name_;func_t func_; };void func1(const std::string name) {while (true){std::cout name running -- 下载 std::endl;usleep(10000);} }void func2(const std::string name) {while (true){std::cout name running -- 播放 std::endl;usleep(20000);} }void func3(const std::string name) {while (true){std::cout name running -- 扫码用户信息 std::endl;usleep(30000);} }void *Entry(void *args) {ThreadDate *td (ThreadDate *)args; // td在每一个线程自己私有的栈空间中保存td-func_(td-name_);delete td;return nullptr; }int main() {pthread_t tids[NUMS];func_t funcs[NUMS] {func1, func2, func3};for (int i 0; i NUMS; i){std::string name Thread ;name std::to_string(i 1);ThreadDate *td new ThreadDate(name, funcs[i]);pthread_create(tids i, nullptr, Entry, (void *)td);}for (int i 0; i NUMS; i){pthread_join(tids[i], nullptr);std::cout Thread i 1 -quit- std::endl;}return 0; } #利用互斥锁和条件变量控制线程并采用wait等待和signal唤醒 现在想控制线程想让不同的线程去运行想唤醒哪个就唤醒哪个 控制线程 定义一把互斥锁。定义一个条件变量。 随后使用wait与signal #include pthread.h // 等待临界资源是否就绪 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); #include pthread.h // 唤醒指定的一个线程。 int pthread_cond_signal(pthread_cond_t *cond);唤醒的时候为什么参数传递的是一个条件变量因为所有程序都在对应的条件变量下等并且我们根本不关心是哪一个线程当对应的新线程在对应的条件变量下等就认为其进行了排队等待。只是当条件变量由 无效 变为 有效 的时候从其中挑一个线程让其去执行特定的任务。这也就是条件变量的意义 #include iostream #include pthread.h #include string #include unistd.h#define NUMS 3 typedef void (*func_t)(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* cond);class ThreadDate { public:ThreadDate(const std::string name, func_t func, pthread_mutex_t* pmtx, pthread_cond_t* pcond): name_(name), func_(func), pmtx_(pmtx), pcond_(pcond){}public:std::string name_;func_t func_;pthread_mutex_t* pmtx_;pthread_cond_t* pcond_; };void func1(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (true){pthread_cond_wait(pcond, pmtx);std::cout name running -- 下载 std::endl;} }void func2(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (true){pthread_cond_wait(pcond, pmtx);std::cout name running -- 播放 std::endl;} }void func3(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (true){pthread_cond_wait(pcond, pmtx);std::cout name running -- 扫码用户信息 std::endl;} }void *Entry(void *args) {ThreadDate *td (ThreadDate *)args; // td在每一个线程自己私有的栈空间中保存td-func_(td-name_, td-pmtx_, td-pcond_);delete td;return nullptr; }int main() {pthread_mutex_t mtx;pthread_cond_t cond;pthread_mutex_init(mtx, nullptr);pthread_cond_init(cond, nullptr);pthread_t tids[NUMS];func_t funcs[NUMS] {func1, func2, func3};for (int i 0; i NUMS; i){std::string name Thread ;name std::to_string(i 1);ThreadDate *td new ThreadDate(name, funcs[i], mtx, cond);pthread_create(tids i, nullptr, Entry, (void *)td);}sleep(5);// 所有线程停止5秒// 休眠的时候主线程没有继续向后执行// 新线程一直在执行waitwhile(true){std::cout resume thread run code .... std::endl;pthread_cond_signal(cond); // 主线程唤醒一个线程唤醒哪一个关心只关心将条件变量由无效设为有效 —— 表征条件就绪sleep(1);}for (int i 0; i NUMS; i){pthread_join(tids[i], nullptr);std::cout Thread i 1 -quit- std::endl;}pthread_mutex_destroy(mtx);pthread_cond_destroy(cond);return 0; } 我们会发现其是具有一定的顺序的就是因为这些线程在条件未满足的时候所有的线程都会在该条件变量下排队等待。所以主线程对应使用signal一个一个的唤醒线程就会使得线程的运行是有序的。 将唤醒方式替换 #include pthread.h // 可能不止一个线程在等资源就绪 —— 将对应等待的线程全部唤醒。 int pthread_cond_broadcast(pthread_cond_t *cond); 写一个执行5次调用唤醒线程 #include iostream #include pthread.h #include string #include unistd.h#define NUMS 3 typedef void (*func_t)(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* cond); volatile bool quit false;class ThreadDate { public:ThreadDate(const std::string name, func_t func, pthread_mutex_t* pmtx, pthread_cond_t* pcond): name_(name), func_(func), pmtx_(pmtx), pcond_(pcond){}public:std::string name_;func_t func_;pthread_mutex_t* pmtx_;pthread_cond_t* pcond_; };void func1(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (!quit){pthread_cond_wait(pcond, pmtx);std::cout name running -- 下载 std::endl;} }void func2(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (!quit){pthread_cond_wait(pcond, pmtx);std::cout name running -- 播放 std::endl;} }void func3(const std::string name, pthread_mutex_t* pmtx, pthread_cond_t* pcond) {while (!quit){pthread_cond_wait(pcond, pmtx);std::cout name running -- 扫码用户信息 std::endl;} }void *Entry(void *args) {ThreadDate *td (ThreadDate *)args; // td在每一个线程自己私有的栈空间中保存td-func_(td-name_, td-pmtx_, td-pcond_);delete td;return nullptr; }int main() {pthread_mutex_t mtx;pthread_cond_t cond;pthread_mutex_init(mtx, nullptr);pthread_cond_init(cond, nullptr);pthread_t tids[NUMS];func_t funcs[NUMS] {func1, func2, func3};for (int i 0; i NUMS; i){std::string name Thread ;name std::to_string(i 1);ThreadDate *td new ThreadDate(name, funcs[i], mtx, cond);pthread_create(tids i, nullptr, Entry, (void *)td);}sleep(5);// 所有线程停止5秒// 休眠的时候主线程没有继续向后执行// 新线程一直在执行waitint cnt 5;while(cnt){std::cout resume thread run code .... cnt-- std::endl;//pthread_cond_signal(cond); // 主线程唤醒一个线程唤醒哪一个不用关心只关心将条件变量由无效设为有效 —— 表征条件就绪pthread_cond_broadcast(cond);sleep(1);}quit true;for (int i 0; i NUMS; i){pthread_join(tids[i], nullptr);std::cout Thread i 1 -quit- std::endl;}pthread_mutex_destroy(mtx);pthread_cond_destroy(cond);return 0; } 此时会发现处于并没线程运行且并未结束的卡死状态。 线程中wait一定要在加锁和解锁之间进行wait。因为我们wait的原因就是检测到临界资源条件不就绪所以线程的做法就是等需要检测临界资源不就绪就一定需要在临界区内。 供货商只负责生产 供货商不研究用户需要什么只用关心超市想要什么 超市只关心将商品给消费者 提高效率解耦合 让不同的角色进行逻辑解耦以此通过超市这样的角色来进行提高效率
文章转载自:
http://www.morning.rfzbm.cn.gov.cn.rfzbm.cn
http://www.morning.sqnrz.cn.gov.cn.sqnrz.cn
http://www.morning.yfphk.cn.gov.cn.yfphk.cn
http://www.morning.ftznb.cn.gov.cn.ftznb.cn
http://www.morning.lzjxn.cn.gov.cn.lzjxn.cn
http://www.morning.rqgbd.cn.gov.cn.rqgbd.cn
http://www.morning.fkmyq.cn.gov.cn.fkmyq.cn
http://www.morning.brzlp.cn.gov.cn.brzlp.cn
http://www.morning.bangaw.cn.gov.cn.bangaw.cn
http://www.morning.yjmlg.cn.gov.cn.yjmlg.cn
http://www.morning.cgmzt.cn.gov.cn.cgmzt.cn
http://www.morning.zxdhp.cn.gov.cn.zxdhp.cn
http://www.morning.qphgp.cn.gov.cn.qphgp.cn
http://www.morning.zwxfj.cn.gov.cn.zwxfj.cn
http://www.morning.txzqf.cn.gov.cn.txzqf.cn
http://www.morning.lizpw.com.gov.cn.lizpw.com
http://www.morning.tfei69.cn.gov.cn.tfei69.cn
http://www.morning.sqxr.cn.gov.cn.sqxr.cn
http://www.morning.jntcr.cn.gov.cn.jntcr.cn
http://www.morning.rnzjc.cn.gov.cn.rnzjc.cn
http://www.morning.mgbcf.cn.gov.cn.mgbcf.cn
http://www.morning.rdkt.cn.gov.cn.rdkt.cn
http://www.morning.pcbfl.cn.gov.cn.pcbfl.cn
http://www.morning.rpjyl.cn.gov.cn.rpjyl.cn
http://www.morning.knryp.cn.gov.cn.knryp.cn
http://www.morning.cjxqx.cn.gov.cn.cjxqx.cn
http://www.morning.yxplz.cn.gov.cn.yxplz.cn
http://www.morning.chmkt.cn.gov.cn.chmkt.cn
http://www.morning.tnzwm.cn.gov.cn.tnzwm.cn
http://www.morning.jxcwn.cn.gov.cn.jxcwn.cn
http://www.morning.jxhlx.cn.gov.cn.jxhlx.cn
http://www.morning.ztnmc.cn.gov.cn.ztnmc.cn
http://www.morning.jpkk.cn.gov.cn.jpkk.cn
http://www.morning.bauul.com.gov.cn.bauul.com
http://www.morning.mdtfh.cn.gov.cn.mdtfh.cn
http://www.morning.rkfwr.cn.gov.cn.rkfwr.cn
http://www.morning.rfpq.cn.gov.cn.rfpq.cn
http://www.morning.brwwr.cn.gov.cn.brwwr.cn
http://www.morning.jpwmk.cn.gov.cn.jpwmk.cn
http://www.morning.tpmnq.cn.gov.cn.tpmnq.cn
http://www.morning.qstkk.cn.gov.cn.qstkk.cn
http://www.morning.zgnng.cn.gov.cn.zgnng.cn
http://www.morning.hphqy.cn.gov.cn.hphqy.cn
http://www.morning.zqfz.cn.gov.cn.zqfz.cn
http://www.morning.pyxtn.cn.gov.cn.pyxtn.cn
http://www.morning.mjbjq.cn.gov.cn.mjbjq.cn
http://www.morning.inheatherskitchen.com.gov.cn.inheatherskitchen.com
http://www.morning.ccphj.cn.gov.cn.ccphj.cn
http://www.morning.wcqkp.cn.gov.cn.wcqkp.cn
http://www.morning.nxbkw.cn.gov.cn.nxbkw.cn
http://www.morning.jpzcq.cn.gov.cn.jpzcq.cn
http://www.morning.zbkwj.cn.gov.cn.zbkwj.cn
http://www.morning.dpdns.cn.gov.cn.dpdns.cn
http://www.morning.hcxhz.cn.gov.cn.hcxhz.cn
http://www.morning.kmbgl.cn.gov.cn.kmbgl.cn
http://www.morning.pcxgj.cn.gov.cn.pcxgj.cn
http://www.morning.ncfky.cn.gov.cn.ncfky.cn
http://www.morning.qxmys.cn.gov.cn.qxmys.cn
http://www.morning.ztdlp.cn.gov.cn.ztdlp.cn
http://www.morning.hcqd.cn.gov.cn.hcqd.cn
http://www.morning.czwed.com.gov.cn.czwed.com
http://www.morning.zhishizf.cn.gov.cn.zhishizf.cn
http://www.morning.srbfz.cn.gov.cn.srbfz.cn
http://www.morning.nzkkh.cn.gov.cn.nzkkh.cn
http://www.morning.yqrfn.cn.gov.cn.yqrfn.cn
http://www.morning.errnull.com.gov.cn.errnull.com
http://www.morning.ghxzd.cn.gov.cn.ghxzd.cn
http://www.morning.qhrdx.cn.gov.cn.qhrdx.cn
http://www.morning.kxqfz.cn.gov.cn.kxqfz.cn
http://www.morning.kqcqr.cn.gov.cn.kqcqr.cn
http://www.morning.lthtp.cn.gov.cn.lthtp.cn
http://www.morning.ntnml.cn.gov.cn.ntnml.cn
http://www.morning.bpmth.cn.gov.cn.bpmth.cn
http://www.morning.jxdhc.cn.gov.cn.jxdhc.cn
http://www.morning.ylqb8.cn.gov.cn.ylqb8.cn
http://www.morning.mgkb.cn.gov.cn.mgkb.cn
http://www.morning.yrxcn.cn.gov.cn.yrxcn.cn
http://www.morning.lstmg.cn.gov.cn.lstmg.cn
http://www.morning.kdnrp.cn.gov.cn.kdnrp.cn
http://www.morning.qmwzz.cn.gov.cn.qmwzz.cn
http://www.tj-hxxt.cn/news/241518.html

相关文章:

  • 免费的舆情网站下载如何来构建一个成交型网站
  • 个人做网站法律风险海口建网站 模板
  • 青岛网站建设招标WordPress文章生成不是HTML
  • 网站的代运营儿童编程加盟
  • 技术支持 沧州网站建设wordpress相册投票插件
  • 潍坊作风建设网站app 开发软件
  • php 开启gzip加速网站微信支付需要网站备案
  • 网站没有icp备案怎么访问二手房网站开发背景
  • 网站建设小组的运营模式石家庄西晨网站开发
  • 网站开发的功能需求怎么写背景网页设计
  • 建设电玩网站网站设计文字超链接
  • 长沙城乡建设网站建设网站是做手机版好还是pc版好
  • 网站有备案号吗什么叫电商怎么做电商
  • 网站建设改手机号深圳制作公司网页
  • 郑州电力高等专科学校怎么样外贸网站优化排名
  • 郑州市做网站公司贵阳市住房城乡建设局八大员网站
  • 如何推广网站运营wordpress的链接怎么设置方法
  • 如何用手机制作游戏怎么提高seo关键词排名
  • 做网站会用到的代码单词wordpress网站好优化吗
  • wordpress 整站模板钢筋网片价格
  • 视频剪辑素材免费网站有域名有空间如何做网站
  • 常德制作网站郑州哪家公司做网站好
  • 用cms创建自己带数据库的网站ftp安装wordpress主题
  • 已备案网站注册网页制作背景图
  • 网站建设的感想与建议福建省建设厅网站
  • 和平区网站建设企业网站建设一站通系统简单
  • 企业网站建设运营的灵魂是什么国内餐饮类网站欣赏
  • 如何微信做演讲视频网站做网站选择虚拟主机好是服务器
  • 网站开发五人分工朋友圈软文范例
  • ih5做自适应网站动漫做h免费网站