火狐浏览器开启的线程。PID进程号 LWP线程号
#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 ; }
线程不是标准系统库 ,而是第三方
因为主线程return 0 退出了,没执行子线程里的。只执行主线程。加了sleep就可以
3.3 终止进程 pthread_exit
#include <stdio.h> #include <pthread.h> #include <string.h> void * callback (void * arg) { printf ("child 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 ("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 ; }
3.4 连接已终止的现场 pthread_join #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> int value = 10 ; void * callback (void * arg) { printf ("child thread id : %ld\n" , pthread_self()); pthread_exit((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()); 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 ; }
3.5 线程的分离 pthread_detach #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * 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); } printf ("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_exit(NULL ); return 0 ; }
试试设置分离后,对分离的子线程进行join
3.6 线程取消 pthread_cancel #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * 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); } printf ("tid : %ld, main thread id : %ld\n" , tid, pthread_self()); pthread_exit(NULL ); return 0 ; }
子线程并没有打印完5个,而是在printf时到达取消点,就终止。
3.7线程属性
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * 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); printf ("tid : %ld, main thread id : %ld\n" , tid, pthread_self()); pthread_attr_destroy(&attr); pthread_exit(NULL ); return 0 ; }
3.8 线程同步 设计 有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 () { 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 ); return 0 ; }
有问题
临界区指tickets
3.9 互斥锁、互斥量 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 ); 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.10 死锁
场景1:忘记释放锁,结果阻塞
场景2:重复加相同锁
场景3:多线程 多锁 抢占锁资源
使用资源有序分配法,线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。
所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。
谈一谈「互斥锁、自旋锁、读写锁、乐观锁、悲观锁 」的选择和使用。
当已经有一个线程加锁后,其他线程加锁则就会失败,互斥锁和自旋锁对于加锁失败后
的处理方式是不一样的:
互斥锁 加锁失败后,线程会释放 CPU ,给其他线程;
自旋锁 加锁失败后,线程会忙等待 ,直到它拿到锁;
互斥锁
是一种「独占锁」,比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞 。对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的 。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。
所以,互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。会有两次线程上下文切换的成本 。所以,如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。
自旋锁是通过 CPU 提供的 CAS
函数(Compare And Swap ),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。
一般加锁的过程,包含两个步骤:
第一步,查看锁的状态,如果锁是空闲的,则执行第二步;
第二步,将锁设置为当前线程持有;
CAS 函数就把这两个步骤合并成一条硬件级指令,形成原子指令 ,这样就保证了这两个步骤是不可分割的,要么一次性执行完两个步骤,要么两个步骤都不执行。
使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会「忙等待」,直到它拿到锁。这里的「忙等待」可以用 while
循环等待实现,不过最好是使用 CPU 提供的 PAUSE
指令来实现「忙等待」,因为可以减少循环等待时的耗电量。需要注意,在单核 CPU 上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。
当加锁失败时,互斥锁用「线程切换」来应对,自旋锁则用「忙等待」来应对 。
写锁是独占锁,因为任何时刻只能有一个线程持有写锁,类似互斥锁和自旋锁,而读锁是共享锁,因为读锁可以被多个线程同时持有。读写锁在读多写少的场景,能发挥出优势。
前面提到的互斥锁、自旋锁、读写锁,都是属于悲观锁。
悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁 。
那相反的,如果多线程同时修改共享资源的概率比较低,就可以采用乐观锁。
乐观锁做事比较乐观,它假定冲突的概率很低,它的工作方式是:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作 。可见,乐观锁的心态是,不管三七二十一,先改了资源再说。另外,你会发现乐观锁全程并没有加锁,所以它也叫无锁编程 。
实际上,我们常见的 SVN 和 Git 也是用了乐观锁的思想,先让用户编辑代码,然后提交的时候,通过版本号来判断是否产生了冲突,发生了冲突的地方,需要我们自己修改后,再重新提交。
乐观锁虽然去除了加锁解锁的操作,但是一旦发生冲突,重试的成本非常高,所以只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。
3.11 读写锁
#include <stdio.h> #include <pthread.h> #include <unistd.h> int num = 1 ;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 ); 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.12 生产者消费者模型
生产者 将东西 生产到容器中,消费者从容器中消费。当容器满了,生产者通知消费者取走东西,当容器为空,消费者通知生产者生产。
条件变量,信号量。
#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 ); 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.13 条件变量 加上互斥锁之后结果:
没问题,这么做但不好。需要生产者和消费者各自判断,最好需要通知方式。
#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 ); 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.14 信号量 semaphore
#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 ); 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 ; }