案例:
买火车票时,有个座位号,第几排第几个座,一排五个座位,买一个座位少一个座位,通过count++%5觉得了买的票在第几个座位,count++/5决定了在第几排;假设开十个线程;
通过如下代码创建线程:
在linux内核中没有进程线程之分,统一叫做task_struct,当调用pthread_create函数时,创建task_struct,创建这个结构体后的状态是new,就绪,等待,执行,退出都是线程的状态;new这个状态是还没有运行的,将其加入到队列中,这个new状态和就绪状态是一样的状态;什么时候调度是由调度器本身所决定的,不是由我们所决定的;pthread_create函数执行后什么时候运行创建的线程是不确定的,线程创建后就加入到了就绪队列中等待着被执行; 当调度器发现创建的func线程没有被执行过的时候,这个线程从new状态开始被回调,并传入count参数;调用pthread_join等待对应线程执行完返回pthread_join再往下执行,pthread_join函数是条件等待;
当有多个线程时对count++时对应的汇编代码,会出现线程1的汇编代码没有执行完,线程2就接着执行的情况;
如何解决上述问题呢?
1、互斥锁ptread_mutex_t,可以干其他事情,获得锁再执行;
;
2、自旋锁pthread_spinlock_t,不会干其他事情,死等直到获得锁再执行;
3、原子操作 ,即能用一条指令执行不可分割;将count++分割成汇编代码封装到inc函数中,成为不可分割的整体,如下汇编代码;
互斥锁和自旋锁和原子操作在这种场景下哪种更好;原子操作最快,所用时间最短;
下图CAS即比较后赋值,单例模式中使用这种方式实现原子操作’
原子操作的经典几个如add,sub,inc,dec,cas;
4、cpu亲缘性
如何更加高效的利用cpu,比如有4核cpu,我们应该创建多少个线程;我们可以创建4个线程分别绑定到绑定到每个cpu上面;操作系统提供了固定的做法;
通过cpu_set_t做粘合,比如4个cpu,进程或线程需要绑定哪个cpu,就将cpu_set_t对应位设置为1;绑定后进程或线程就只在对应cpu上运行;
上面代码中,cpu_set_t设置cpu组,这里时4个cpu,然后通过CPU_ZERO把mask清空,然后对进程self_id取模并使用这个值对mask对应位置1;然后通过sched_setaffinity对进程和cpu做粘合;粘合和进程就会只在对应cpu上运行;两个进程可以绑定在一个cpu上面;当一个进程有多个线程时,绑定的是主线程,子线程得通过子线程id去绑定cpu;
5、shmem,共享内存的做法;
一个大文件,如何快速的读到内存中;把文件通过mmap映射到内存中,然后开启多个线程去读数据;
mmap为什么快?磁盘的数据读到内存中通常通过cpu参与一一执行;而mmap映射是通过dma的方式,当我们一开始数据就已经到内存中了;dma是磁盘和内存间数据传输的一种方式,cpu不用参与;我们所说的零拷贝,是cpu不参与;mmap是实现共享内存的一种方式;
可通过shm一系列接口实现映射内存;对应nginx代码如下