侵权投诉

一文带你彻底搞懂多线程中各个难点

硬件攻城狮 2021-06-30 15:00 次阅读

1.什么是线程?

linux内核中是没有线程这个概念的,而是轻量级进程的概念:LWP。一般我们所说的线程概念是C库当中的概念。

1.1线程是怎样描述的?

线程实际上也是一个task_struct,工作线程拷贝主线程的task_struct,然后共用主线程的mm_struct。线程ID是在用task_struct中pid描述的,而task_struct中tgid是线程组ID,表示线程属于该线程组,对于主线程而言,其pid和tgid是相同的,我们一般看到的进程ID就是tgid。

即:

但是获取该gettid系统调用接口并没有被封装起来,如果确实需要获取线程ID,可使用:

#include 《sys/syscall.h》

int TID = syscall(SYS_gettid);

则对线程组而言,所有的tgid一定是一样的,所有的pid一定是不一样的。主线程pid和tgid一样,工作线程pid和tgid一定不一样。

1.2如何查看一个线程的ID

命令:ps -eLf

上述polkitd进程是多线程的,进程ID为731,进程内有6个线程,线程ID为731,764,765,768,781,791。

1.3多线程如何避免调用栈混乱的问题?

工作线程和主线程共用一个mm_struct,如果都向栈中压栈,必然会导致调用栈出错。

实际上工作线程压栈是压了共享区,该共享区包含了许多线程独有的资源。如图:

每一个线程,默认在共享区中占有的空间为8M,可以使用ulimit -s修改。

进程是资源分配的基本单位,线程是调度的基本单位。

1.3.1线程独有资源

线程ID

一组寄存器

errno

信号屏蔽字

调度优先级

1.3.2线程共享资源和环境

文件描述符表

信号的处理方式

当前工作目录

用户id和组id

1.4为什么要有多线程?

举个生活中的例子, 这就好比去银行办理业务。到达银行后, 首先取一个号码, 然后坐下来安心等待。这时候你一定希望, 办理业务的窗口越多越好。如果把整个营业大厅当成一个进程的话, 那么每一个窗口就是一个工作线程。

1.4.1线程带来的优势

1、线程会共享内存地址空间。

2、创建线程花费的时间要少于创建进程花费的时间。

3、终止线程花费的时间要少于终止进程花费的时间。

4、线程之间上下文切换的开销, 要小于进程之间的上下文切换。

5、线程之间数据的共享比进程之间的共享要简单。

6、充分利用多处理器的可并行数量。(线程会提高运行效率,但当线程多到一定程度后,可能会导致效率下降,因为会有线程调度切换。)

1.4.2线程带来的缺点

健壮性降低:多个线程之中, 只要有一个线程不够健壮存在bug(如访问了非法地址引发的段错误) , 就会导致进程内的所有线程一起完蛋。

线程模型作为一种并发的编程模型, 效率并没有想象的那么高, 会出现复杂度高、 易出错、 难以测试和定位的问题。

1.5注意

1、并不是只有主线程才能创建线程, 被创建出来的线程同样可以创建线程。

2、不存在类似于fork函数那样的父子关系, 大家都归属于同一个线程组, 进程ID都相等, group_leader都指向主线程, 而且各有各的线程ID。

通过group_leader指针, 每个线程都能找到主线程。主线程存在一个链表头,后面创建的每一个线程都会链入到该双向链表中。

3、并非只有主线程才能调用pthread_join连接其他线程, 同一线程组内的任意线程都可以对某线程执行pthread_join函数。

4、并非只有主线程才能调用pthread_detach函数, 其实任意线程都可以对同一线程组内的线程执行分离操作。

线程的对等关系:

2.线程创建

接口:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数解释

1、thread:线程标识符,是一个出参

2、attr:线程属性

3、star_routine:函数指针,保存线程入口函数的地址

4、arg:给线程入口函数传参

返回值:成功返回0,失败返回error number

详解:

第一个参数是pthread_t类型的指针, 线程创建成功的话,会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。

第二个参数是pthread_attr_t类型, 通过该参数可以定制线程的属性, 比如可以指定新建线程栈的大小、 调度策略等。如果创建线程无特殊的要求, 该值也可以是NULL, 表示采用默认属性。

第三个参数是线程需要执行的函数。创建线程, 是为了让线程执行一定的任务。线程创建成功之后, 该线程就会执行start_routine函数, 该函数之于线程, 就如同main函数之于主线程。

第四个参数是新建线程执行的start_routine函数的入参。

2.1传入参数arg的选择

不要使用临时变量传参,使用堆上开辟的变量可以。

例:

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《unistd.h》

void *ThreadWork(void *arg)

{

int *p = (int*)arg;

printf(“i am work thread:%p, data:%d

”,pthread_self(),*p);

pthread_exit(NULL);

}

int main()

{

int i = 1;

pthread_t tid;

int ret = pthread_create(&tid,NULL,ThreadWork,(void*)&i);//不要传临时变量,这里是示范

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

while(1)

{

printf(“i am main work thread

”);

sleep(1);

}

return 0;

}

2.2线程ID以及进程地址空间

线程获取自身的ID:

#include 《pthread.h》

pthread_t pthread_self(void);

判断两个线程ID是否对应着同一个线程:

#include 《pthread.h》

int pthread_equal(pthread_t t1, pthread_t t2);

返回为0时,则表示两个线程为同一个线程,非0时,表示不是同一个线程。

用户调用pthread_create函数时, 首先要为线程分配线程栈, 而线程栈的位置就落在共享区。调用mmap函数为线程分配栈空间。pthread_create函数分配的pthread_t类型的线程ID, 不过是分配出来的空间里的一个地址, 更确切地说是一个结构体的指针。

2.3线程注意点

1、线程ID是进程地址空间内的一个地址, 要在同一个线程组内进行线程之间的比较才有意义。不同线程组内的两个线程, 哪怕两者的pthread_t值是一样的, 也不是同一个线程。

2、线程ID就有可能会被复用:

1、线程退出。

2、线程组的其他线程对该线程执行了pthread_join, 或者线程退出前将分离状态设置为已分离。

3、再次调用pthread_create创建线程。

2.4线程创建出来的默认值

线程创建的第二个参数是pthread_attr_t类型的指针, pthread_attr_init函数会将线程的属性重置成默认值。

如果确实需要很多的线程, 可以调用接口来调整线程栈的大小:

#include 《pthread.h》

int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);

int pthread_attr_getstacksize(pthread_attr_t *attr,size_t *stacksize);

3.线程终止

线程终止,但进程不会终止的方法:

1、入口函数的return返回,线程就退出了

2、线程调用pthread_exit(NULL),谁调用谁退出

#include 《pthread.h》

void pthread_exit(void *retval);

参数:retval是返回信息,”临终遗言“,可以给可以不给

该变量不能使用临时变量。

可使用:全局变量、堆上开辟的空间、字符串常量。

pthread_exit和线程启动函数(start_routine) 执行return是有区别的。在start_routine中调用的任何层级的函数执行pthread_exit() 都会引发线程退出, 而return, 只能是在start_routine函数内执行才能导致线程退出。

3、其它线程调用了pthread_cancel函数取消了该线程

int pthread_cancel(pthread_t thread);

thread:线程标识符

调用该函数的执行流可以取消其它线程,但是需要知道其它线程的线程标识符,也可以执行流自己取消自己,传入自己的线程标识符。

如果线程组中的任何一个线程调用了exit函数, 或者主线程在main函数中执行了return语句, 那么整个线程组内的所有线程都会终止。

4.线程等待

4.1线程等待接口

#include 《pthread.h》

int pthread_join(pthread_t thread, void **retval);

调用该函数,该执行流在等待线程退出的时候,该执行流是阻塞在pthread_joind当中的。

4.2线程等待和进程等待的不同

第一点不同之处是进程之间的等待只能是父进程等待子进程, 而线程则不然。线程组内的成员是对等的关系, 只要是在一个线程组内, 就可以对另外一个线程执行连接(join) 操作。

第二点不同之处是进程可以等待任一子进程的退出 , 但是线程的连接操作没有类似的接口, 即不能连接线程组内的任一线程, 必须明确指明要连接的线程的线程ID。

4.3为什么要等待退出的线程?

如果不连接已经退出的线程, 会导致资源无法释放。所谓资源指的又是什么呢?

1、已经退出的线程, 其空间没有被释放, 仍然在进程的地址空间之内。

2、新创建的线程, 没有复用刚才退出的线程的地址空间。

如果不执行连接操作, 线程的资源就不能被释放, 也不能被复用, 这就造成了资源的泄漏。

纵然调用了pthread_join, 也并没有立即调用munmap来释放掉退出线程的栈, 它们是被后建的线程复用了。释放线程资源的时候, 若进程可能再次创建线程, 而频繁地munmap和mmap会影响性能, 所以将该栈缓存起来, 放到一个链表之中, 如果有新的创建线程的请求, 会首先在栈缓存链表中寻找空间合适的栈, 有的话, 直接将该栈分配给新创建的线程。

例:

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《unistd.h》

#include 《sys/syscall.h》

void *ThreadWork(void *arg)

{

int *p = (int*)arg;

printf(“pid : %d

”,syscall(SYS_gettid));

printf(“i am work thread:%p, data:%d

”,pthread_self(),*p);

sleep(3);

pthread_exit(NULL);

}

int main()

{

int i = 1;

pthread_t tid;

int ret = pthread_create(&tid,NULL,ThreadWork,(void*)&i);//不要传临时变量,这里是示范

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

pthread_join(tid,NULL);//线程等待

while(1)

{

printf(“i am main work thread

”);

sleep(1);

}

return 0;

}

5.线程分离

接口:#include 《pthread.h》

int pthread_detach(pthread_t thread);

默认情况下, 新创建的线程处于可连接(Joinable) 的状态, 可连接状态的线程退出后, 需要对其执行连接操作, 否则线程资源无法释放, 从而造成资源泄漏。

如果其他线程并不关心线程的返回值, 那么连接操作就会变成一种负担:你不需要它, 但是你不去执行连接操作又会造成资源泄漏。这时候你需要的东西只是:线程退出时, 系统自动将线程相关的资源释放掉, 无须等待连接。

可以是线程组内其他线程对目标线程进行分离, 也可以是线程自己执行pthread_detach函数。

线程的状态之中, 可连接状态和已分离状态是冲突的, 一个线程不能既是可连接的, 又是已分离的。因此, 如果线程处于已分离的状态, 其他线程尝试连接线程时, 会返回EINVAL错误。

注意:这里的已分离不是指线程失去控制,不归线程组管,而是指线程退出后,系统会自动释放线程资源。若是线程组内的任意线程执行了exit函数,即使是已分离的线程,也仍会收到影响,一并退出。

6.线程安全

线程安全中涉及到的概念:

临界资源:多线程中都能访问到的资源

临界区:每个线程内部,访问临界资源的代码,就叫临界区

6.1什么是线程不安全?

多个线程访问同一块临界资源,导致资源产生二义性的现象。

6.1.1举一个例子

假设现在有两个线程A和B,单核CPU的情况下,此时有一个int类型的全局变量为100,A和B的入口函数都要对这个全局变量进行–操作。

线程A先拿到CPU资源后,对全局变量进行–操作并不是原子性操作,也就是意味着,A在执行–的过程中有可能会被打断。假设A刚刚将全局变量的值读到寄存器当中,就被切换出去了,此时程序计数器保存了下一条执行的指令,上下文信息保存寄存器中的值,这两个东西是用来线程A再次拿到CPU资源后,恢复现场使用的。

此时,线程B拿到了CPU资源,对全局变量进行了–操作,并且将100减为了99,回写到了内存中。

A再次拥有了CPU资源后,恢复现场,继续往下执行,从寄存器中读到的值仍为100,减完之后为99,回写到内存中为99。

上述例子中,线程A和B都对全局变量进行了–操作,全局变量的值应该变为98,但程序现在实际的结果为99,所以这就导致了线程不安全。

6.2如何解决线程不安全现象?

解决方案只需做到下述三点即可:

1、代码必须要有互斥的行为:当一个线程正在临界区中执行时, 不允许其他线程进入该临界区中。

2、如果多个线程同时要求执行临界区的代码, 并且当前临界区并没有线程在执行, 那么只能允许一个线程进入该临界区。

3、如果线程不在临界区中执行, 那么该线程不能阻止其他线程进入临界区。

锁是一个很普遍的需求, 当然用户可以自行实现锁来保护临界区。但是实现一个正确并且高效的锁非常困难。纵然抛下高效不谈, 让用户从零开始实现一个正确的锁也并不容易。正是因为这种需求具有普遍性, 所以Linux提供了互斥量。

6.3互斥量接口

6.3.1互斥量的初始化

1、静态分配:

#include 《pthread.h》

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2、动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

调用int pthread_mutex_init()函数后,互斥量是处于没有加锁的状态。

6.3.2互斥量的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:

1、使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无须销毁。

2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。

3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。

当互斥量处于已加锁的状态, 或者正在和条件变量配合使用, 调用pthread_mutex_destroy函数会返回EBUSY错误码。

6.3.3互斥量的加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

第一个接口:int pthread_mutex_lock(pthread_mutex_t *mutex);

1、该接口是阻塞加锁接口。

2、mutex为传入互斥锁变量的地址

3、如果mutex当中的计数器为1,pthread_mutex_lock接口就返回了,表示加锁成功,同时计数器当中的值会被更改为0.

4、如果mutex当中的计数器为0,pthread_mutex_lock接口就阻塞了,pthread_mutex_lock接口没有返回了,阻塞在函数内部,直到加锁成功

第二个接口:int pthread_mutex_trylock(pthread_mutex_t *mutex);

1、该接口为非阻塞接口

2、mutex中计数器为1时,加锁成功,计数器置为0,然后返回

3、mutex中计数器为0时,加锁失败,但也会返回,此时加锁是失败状态,一定不要去访问临界资源

4、非阻塞接口一般都需要搭配循环来使用。

第三个接口:int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

1、带有超时时间的加锁接口

2、不能直接获取互斥锁的时候,会等待abs_timeout时间

3、如果在这个时间内加锁成功了,直接返回,不需要再继续等待剩余的时间,并且表示加锁成功

4、如果超出了该时间,也返回了,但是加锁失败了,需要循环加锁

上述三个加锁接口,第一个接口用的最多。

6.3.4互斥量的解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

对上述所有的加锁接口,都可使用该函数解锁

解锁的时候,会将互斥锁当中计数器的值从0变为1,表示其它线程可以获取互斥量

6.4互斥锁的本质

1、在互斥锁内部有一个计数器,其实就是互斥量,计数器的值只能为0或者为1

2、当线程获取互斥锁的时候,如果计数器当前值为0,表示当前线程不能获取到互斥锁,也就是没有获取到互斥锁,就不要去访问临界资源

3、当前线程获取互斥锁的时候,如果计数器当前值为1,表示当前线程可以获取到互斥锁,也就是意味着可以访问临界资源

6.5互斥锁中的计数器如何保证了原子性?

获取锁资源的时候(加锁):

1、寄存器当中值直接赋值为0

2、将寄存器当中的值和计数器当中的值进行交换

3、判断寄存器当中的值,得出加锁结果

例:4个线程,对同一个全局变量进行减减操作

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《unistd.h》

#include 《sys/syscall.h》

#define NUMBER 4

int g_val = 100;

pthread_mutex_t mutex;//定义互斥锁

void *ThreadWork(void *arg)

{

int *p = (int*)arg;

pthread_detach(pthread_self());//自己分离自己,不用主线程回收它的资源了

while(1)

{

pthread_mutex_lock(&mutex);//加锁

if(g_val 》 0)

{

printf(“i am pid : %d,i get g_val : %d

”,(int)syscall(SYS_gettid),g_val);

--g_val;

usleep(2);

}

else{

pthread_mutex_unlock(&mutex);//在所有可能退出的地方,进行解锁

break;

}

pthread_mutex_unlock(&mutex);//解锁

}

pthread_exit(NULL);

}

int main()

{

pthread_t tid[NUMBER];

pthread_mutex_init(&mutex,NULL);//互斥锁初始化

int i = 0;

for(;i 《 NUMBER;++i)

{

int ret = pthread_create(&tid[i],NULL,ThreadWork,(void*)&g_val);//不要传临时变量,这里是示范

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

}

//pthread_join(tid,NULL);//线程等待

//pthread_detach(tid);//线程分离

pthread_mutex_destroy(&mutex);//销毁互斥锁

while(1)

{

printf(“i am main work thread

”);

sleep(1);

}

return 0;

}

6.6互斥锁公平嘛?

互斥锁是不公平的。

内核维护等待队列, 互斥量实现了大体上的公平;由于等待线程被唤醒后, 并不自动持有互斥量, 需要和刚进入临界区的线程竞争(抢锁), 所以互斥量并没有做到先来先服务。

6.7互斥锁的类型

1、PTHREAD_MUTEX_NORMAL:最普通的一种互斥锁。它不具备死锁检测功能, 如线程对自己锁定的互斥量再次加锁, 则会发生死锁。

2、

PTHREAD_MUTEX_RECURSIVE_NP:支持递归的一种互斥锁, 该互斥量的内部维护有互斥锁的所有者和一个锁计数器。当线程第一次取到互斥锁时, 会将锁计数器置1, 后续同一个线程再次执行加锁操作时, 会递增该锁计数器的值。解锁则递减该锁计数器的值, 直到降至0, 才会真正释放该互斥量, 此时其他线程才能获取到该互斥量。解锁时, 如果互斥量的所有者不是调用解锁的线程, 则会返回EPERM。

3、

PTHREAD_MUTEX_ERRORCHECK_NP:支持死锁检测的互斥锁。互斥量的内部会记录互斥锁的当前所有者的线程ID(调度域的线程ID) 。如果互斥量的持有线程再次调用加锁操作, 则会返回EDEADLK。解锁时, 如果发现调用解锁操作的线程并不是互斥锁的持有者, 则会返回EPERM。

4、自旋锁,自旋锁采用了和互斥量完全不同的策略, 自旋锁加锁失败, 并不会让出CPU, 而是不停地尝试加锁, 直到成功为止。这种机制在临界区非常小且对临界区的争夺并不激烈的场景下, 效果非常好。自旋锁的效果好, 但是副作用也大, 如果使用不当, 自旋锁的持有者迟迟无法释放锁, 那么, 自旋接近于死循环, 会消耗大量的CPU资源, 造成CPU使用率飙高。因此, 使用自旋锁时, 一定要确保临界区尽可能地小, 不要有系统调用, 不要调用sleep。使用strcpy/memcpy等函数也需要谨慎判断操作内存的大小, 以及是否会引起缺页中断。

5、PTHREAD_MUTEX_ADAPTIVE_NP:自适应锁,首先与自旋锁一样, 持续尝试获取, 但过了一定时间仍然不能申请到锁, 就放弃尝试, 让出CPU并等待。PTHREAD_MUTEX_ADAPTIVE_NP类型的互斥量, 采用的就是这种机制。

6.8死锁和活锁

线程1已经成功拿到了互斥量1, 正在申请互斥量2, 而同时在另一个CPU上,线程2已经拿到了互斥量2, 正在申请互斥量1。彼此占有对方正在申请的互斥量,结局就是谁也没办法拿到想要的互斥量, 于是死锁就发生了。

6.8.1死锁概念

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其它进程所占有不会释放的资源而处于一种永久等待的状态。

6.8.2死锁的四个必要条件

1、互斥条件:一个资源只能被一个执行流使用

2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源不会释放

3、不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺

4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

6.8.3避免死锁

1、破坏死锁的四个必要条件(实际上只能破坏条件2和4)

2、加锁顺序一致(按照先后顺序申请互斥锁)

3、避免未释放锁的情况

4、资源一次性分配

6.8.4活锁

避免死锁的另一种方式是尝试一下,如果取不到锁就返回。

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);

这两个函数反映了一种,不行就算了的思想。

trylock不行就回退的思想有可能会引发活锁(live lock) 。生活中也经常遇到两个人迎面走来, 双方都想给对方让路, 但是让的方向却不协调, 反而互相堵住的情况 。活锁现象与这种场景有点类似。

线程1首先申请锁mutex_a后, 之后尝试申请mutex_b, 失败以后, 释放mutex_a进入下一轮循环, 同时线程2会因为尝试申请mutex_a失败,而释放mutex_b, 如果两个线程恰好一直保持这种节奏, 就可能在很长的时间内两者都一次次地擦肩而过。当然这毕竟不是死锁, 终究会有一个线程同时持有两把锁而结束这种情况。尽管如此, 活锁的确会降低性能。

6.8.5死锁调试

查看多个线程堆栈:thread apply all bt

跳转到线程中:t 线程号

查看具体的调用堆栈:f 堆栈号

直接从pid号用gdb调试:gdb attach pid

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《unistd.h》

#include 《sys/syscall.h》

#define NUMBER 2

pthread_mutex_t mutex1;//定义互斥锁

pthread_mutex_t mutex2;

void *ThreadWork1(void *arg)

{

int *p = (int*)arg;

pthread_mutex_lock(&mutex1);

sleep(2);

pthread_mutex_lock(&mutex2);

pthread_mutex_unlock(&mutex2);

pthread_mutex_unlock(&mutex1);

return NULL;

}

void *ThreadWork2(void *arg)

{

int *p = (int*)arg;

pthread_mutex_lock(&mutex2);

sleep(2);

pthread_mutex_lock(&mutex1);

pthread_mutex_unlock(&mutex1);

pthread_mutex_unlock(&mutex2);

return NULL;

}

int main()

{

pthread_t tid[NUMBER];

pthread_mutex_init(&mutex1,NULL);//互斥锁初始化

pthread_mutex_init(&mutex2,NULL);//互斥锁初始化

int i = 0;

int ret = pthread_create(&tid[0],NULL,ThreadWork1,(void*)&i);

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

ret = pthread_create(&tid[1],NULL,ThreadWork2,(void*)&i);

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

//pthread_join(tid,NULL);//线程等待

//pthread_join(tid,NULL);//线程等待

//pthread_detach(tid);//线程分离

pthread_join(tid[0],NULL);

pthread_join(tid[1],NULL);

pthread_mutex_destroy(&mutex1);//销毁互斥锁

pthread_mutex_destroy(&mutex2);//销毁互斥锁

while(1)

{

printf(“i am main work thread

”);

sleep(1);

}

return 0;

}

在上述代码中,一定会出现死锁,线程1拿到了互斥锁1,又再去申请线程2的互斥锁2,线程2拿到了互斥锁2又再去申请线程1的互斥锁1。

开始调试:

1、找到进程号

2、开始调试

3、查看多个线程堆栈

4、跳转到线程中

5、查看具体调用堆栈

6、查看互斥锁1和互斥锁2,分别被谁拿着

6.9读写锁

6.9.1什么是读写锁?

大部分情况下,对于共享变量的访问特点:只是读取共享变量的值,而不是修改,只有在少数情况下,才会真正的修改共享变量的值。

在这种情况下,读请求之间是同步的,它们之间的并发访问是安全的。然而写请求必须锁住读请求和其它写请求。

即读线程可多个同时读,而写线程只允许同一时间内一个线程去写。

6.9.2读写锁接口

#include 《pthread.h》

//销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

//初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

const pthread_rwlockattr_t *restrict attr);

对于调用pthread_rwlock_init初始化的读写锁,在不需要读写锁的时候,需要调用pthread_rwlock_destroy销毁。

6.9.3读者加锁

#include 《pthread.h》

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //阻塞类型的读加锁接口

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //非阻塞类型的读加锁接口

最大的好处就是,允许多个线程以只读加锁的方式获取到读写锁;

本质上,读写锁的内部维护了一个引用计数,每当线程以读方式获取读写锁时,该引用计数+1;

当释放以读加锁的方式的读写锁时,会先对引用计数进行-1,直到引用计数的值为0的时候,才真正释放了这把读写锁。

6.9.4写者加锁

#include 《pthread.h》

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);// 非阻塞写

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//阻塞写

写锁用的是独占模式,如果当前读写锁被某写线程占用着,则不允许任何读锁通过请求,也不允许任何写锁请求通过,读锁请求和写锁请求都要陷入阻塞,直到线程释放写锁。

6.9.5 解锁

#include 《pthread.h》

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

不论是读者加锁还是写者加锁,都采用该接口进行解释。

读者解锁,只有当引用计数为0的时候,才真正释放了读写锁。

6.9.6读写锁的竞争策略

对于读写锁而言,目前有两种策略,读者优先和携着优先;

读写锁的类型有如下几种:

PTHREAD_RWLOCK_PREFER_READER_NP, //读者优先

PTHREAD_RWLOCK_PREFER_WRITER_NP, //很唬人, 但是也是读者优先

PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, //写者优先

PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP

读者优先:读锁来请求可以立即响应,只要有一个读锁没完成,那么写锁就无法写。这种策略是不公平的,极端情况下,写现场很可能被饿死,即线程总是拿不到锁资源。

写者优先:只要线程申请了写锁,那么在写锁后面到来的读锁请求就会统统被阻塞,不能先于写锁拿到锁。

读写锁实现中的变量及含义

对于读请求而言:如果

1. 无线程持有写锁,即_writer = 0.

2. 采用读者优先策略或者当前没有写锁申请请求,即 _nr_writers_queue = 0

3. 当满足这两个条件时,读锁请求立即获得读锁,返回之前执行_nr_readers++,表示多了一个线程正在读

4. 不满足这两个条件时,执行_nr_readers_queued++,表示增加了一个读锁等待者,然后调用futex,陷入阻塞。醒来之后,执行_nr_readers_queued- -,再次判断是否满足条件1,2

对于写请求而言:如果

1. 无线程持有写锁,即_writer = 0.

2. 没有线程持有读锁,即_nr_readers = 0.

3. 如果上述条件满足,就会立即拿到锁,将_writer 置为当前线程的ID

4. 如果不满足,则执行_nr_writers_queue++, 表示增加了一个写锁等待者线程,然后执行futex陷入等待。醒来后,先执行_nr_writers_queue- -,再继续判断条件1,2

对于解锁,如果当前是写锁:

1. 执行_writer = 0.,表示释放写锁。

2. 根据_nr_writers_queue判断有没有写锁,如果有则唤醒一个写锁,如果没有写锁等待者,则唤醒所有的读锁等待者。

对于解锁,如果当前是读锁:

1. 执行_nr_readers- -,表示读锁占有者少了一个。

2. 判断_nr_readers是否等于0,是的话则表示当前线程是最后一个读锁占有者,需要唤醒写锁等待者或读锁等待者

3. 根据_nr_writers_queue判断是否存在写锁等待者,若有,则唤醒一个写锁等待线程

4. 如果没有写锁等待者,判断是否存在读锁等待者,若有,则唤醒全部的读锁等待者

读写锁很容易造成,读者饿死或者写者饿死。

也可以设计公平的读写锁。

代码:

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《sys/syscall.h》

#include 《unistd.h》

#include 《fcntl.h》

#define THREADCOUNT 100

static int count = 0;

static pthread_rwlock_t lock;

void* Read(void* i)

{

while(1)

{

pthread_rwlock_rdlock(&lock);

printf(“i am 读线程 : %d, 现在的count是%d

”, (int)syscall(SYS_gettid), count);

pthread_rwlock_unlock(&lock);

//sleep(1);

}

}

void* Write(void* i)

{

while(1)

{

pthread_rwlock_wrlock(&lock);

++count;

printf(“i am 写线程 : %d, 现在的count是: %d

”, (int)syscall(SYS_gettid), count);

pthread_rwlock_unlock(&lock);

sleep(1);

}

}

int main()

{

//close(1);

//int fd = open(“。/dup2_result.txt”, O_CREAT | O_RDWR);

//dup2(fd, 1);

pthread_t tid[THREADCOUNT];

pthread_rwlock_init(&lock, NULL);

for(int i = 0; i 《 THREADCOUNT; ++i)

{

if(i % 2 == 0)

{

pthread_create(&tid[i], NULL, Read, (void*)&i);

}

else

{

pthread_create(&tid[i], NULL, Write, (void*)&i);

}

}

for(int i = 0; i 《 THREADCOUNT; ++i)

{

pthread_join(tid[i], NULL);

}

pthread_rwlock_destroy(&lock);

return 0;

}

上述代码很容易触发线程饿死。

读饿死或者写饿死。

7.线程间同步

7.1为什么需要线程同步?

线程同步是为了对临界资源访问的合理性。

例如:

就像工厂里生产车间没有原料了, 所有生产车间都停工了, 工人们都在车间睡觉。突然进来一批原料, 如果原料充足, 你会发广播给所有车间, 原料来了, 快来开工吧。如果进来的原料很少, 只够一个车间开工的, 你可能只会通知一个车间开工。

7.2如何做到线程间同步?

条件等待是线程间同步的另一种方法。

如果条件不满足, 它能做的事情就是等待, 等到条件满足为止。通常条件的达成, 很可能取决于另一个线程, 比如生产者-消费者模型。当另外一个线程发现条件符合的时候, 它会选择一个时机去通知等待在这个条件上的线程。有两种可能性, 一种是唤醒一个线程, 一种是广播, 唤醒其他线程。

则在这个情况下,需要做到:

1、线程在条件不满足的情况下, 主动让出互斥量, 让其他线程去折腾, 线程在此处等待, 等待条件的满足;

2、一旦条件满足, 线程就可以立刻被唤醒。

3、线程之所以可以安心等待, 依赖的是其他线程的协作, 它确信会有一个线程在发现条件满足以后, 将向它发送信号, 并且让出互斥量。

7.3条件变量

本质上是PCB等待队列 + 等待接口 + 唤醒接口。

7.3.1条件变量的初始化

静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态初始化

pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);

7.3.2条件变量的等待

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict conpthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

为什么这两个接口中有互斥锁?

条件不会无缘无故地突然变得满足了, 必然会牵扯到共享数据的变化。所以一定要有互斥锁来保护。没有互斥锁, 就无法安全地获取和修改共享数据。

同步并没有保证互斥,而保证互斥是使用到了互斥锁。

pthread_mutex_lock(&m)

while(condition_is_false)

{

pthread_mutex_unlock(&m);

//解锁之后, 等待之前, 可能条件已经满足, 信号已经发出, 但是该信号可能会被错过

cond_wait(&cv);

pthread_mutex_lock(&m);

}

上面的解锁和等待不是原子操作。解锁以后, 调用cond_wait之前,如果已经有其他线程获取到了互斥量, 并且满足了条件, 同时发出了通知信号, 那么cond_wait将错过这个信号, 可能会导致线程永远处于阻塞状态。所以解锁加等待必须是一个原子性的操作, 以确保已经注册到事件的等待队列之前, 不会有其他线程可以获得互斥量。

那先注册等待事件, 后释放锁不行吗?注意, 条件等待是个阻塞型的接口, 不单单是注册在事件的等待队列上, 线程也会因此阻塞于此, 从而导致互斥量无法释放, 其他线程获取不到互斥量, 也就无法通过改变共享数据使等待的条件得到满足, 因此这就造成了死锁。

pthread_mutex_lock(&m);

while(condition_is_false)

pthread_cond_wait(&v,&m);//此处会阻塞

/*如果代码运行到此处, 则表示我们等待的条件已经满足了,

*并且在此持有了互斥量

*/

/*在满足条件的情况下, 做你想做的事情。

*/

pthread_mutex_unlock(&m);

pthread_cond_wait函数只能由拥有互斥量的线程来调用, 当该函数返回的时候, 系统会确保该线程再次持有互斥量, 所以这个接口容易给人一种误解, 就是该线程一直在持有互斥量。事实上并不是这样的。这个接口向系统声明了我在PCB等待序列中之后, 就把互斥量给释放了。这样其他线程就有机会持有互斥量,操作共享数据, 触发变化, 使线程等待的条件得到满足。

pthread_cond_wait内部会进行解锁逻辑,则一定要先放到PCB等待序列中,再进行解锁。

while(condition_is_false)

pthread_cond_wait(&v,&m);//此处会阻塞

if(condition_is_false)

pthread_cond_wait(&v,&m);//此处会阻塞

唤醒以后, 再次检查条件是否满足, 是不是多此一举?

因为唤醒中存在虚假唤醒(spurious wakeup) , 换言之,条件尚未满足, pthread_cond_wait就返了。在一些实现中, 即使没有其他线程向条件变量发送信号, 等待此条件变量的线程也有可能会醒来。

条件满足了发送信号, 但等到调用pthread_cond_wait的线程得到CPU资源时, 条件又再次不满足了。好在无论是哪种情况, 醒来之后再次测试条件是否满足就可以解决虚假等待的问题。

pthread_cond_wait内部实现逻辑:

将调用pthread_cond_wait函数的执行流放入到PCB等待队列当中

解锁

等待被唤醒

被唤醒之后:

1、从PCB等待队列中移除出来

2、抢占互斥锁

情况1:拿到互斥锁,pthread_cond_wait就返回了

情况2:没有拿到互斥锁,阻塞在pthread_cond_wait内部抢锁的逻辑中

当阻塞在pthread_cond_wait函数抢锁逻辑中时,一旦执行流时间耗尽,意味着线程就被切换出来了,程序计数器就保存的是抢锁的指令,上下文信息保存的就是寄存器的值

当再次拥有CPU资源后,恢复抢锁逻辑

直到抢锁成功,pthread_cond_wait函数才会返回

7.3.3条件变量的唤醒

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_signal负责唤醒等待在条件变量上的一个线程。

pthread_cond_broadcast,就是广播唤醒等待在条件变量上的所有线程。

先发送信号,然后解锁互斥量,这个顺序是必须的嘛?

先通知条件变量、 后解锁互斥量, 效率会比先解锁、 后通知条件变量低。因为先通知后解锁, 执行pthread_cond_wait的线程可能在互斥量已然处于加锁状态的时候醒来, 发现互斥量仍然没有解锁, 就会再次休眠, 从而导致了多余的上下文切换。

7.3.4条件变量的销毁

int pthread_cond_destroy(pthread_cond_t *cond);

注意:

1、永远不要用一个条件变量对另一个条件变量赋值, 即pthread_cond_t cond_b = cond_a不合法, 这种行为是未定义的。

2、使用PTHREAD_COND_INITIALIZE静态初始化的条件变量, 不需要被销毁。

3、要调用pthread_cond_destroy销毁的条件变量可以调用pthread_cond_init重新进行初始化。

4、不要引用已经销毁的条件变量, 这种行为是未定义的。

例:

#include 《stdio.h》

#include 《stdlib.h》

#include 《pthread.h》

#include 《unistd.h》

#include 《sys/syscall.h》

#define NUMBER 2

int g_bowl = 0;

pthread_mutex_t mutex;//定义互斥锁

pthread_cond_t cond1;//条件变量

pthread_cond_t cond2;//条件变量

void *WorkProduct(void *arg)

{

int *p = (int*)arg;

while(1)

{

pthread_mutex_lock(&mutex);

while(*p 》 0)

{

pthread_cond_wait(&cond2,&mutex);//条件等待,条件不满足,陷入阻塞

}

++(*p);

printf(“i am workproduct :%d,i product %d

”,(int)syscall(SYS_gettid),*p);

pthread_cond_signal(&cond1);//通知消费者

pthread_mutex_unlock(&mutex);//释放锁

}

return NULL;

}

void *WorkConsume(void *arg)

{

int *p = (int*)arg;

while(1)

{

pthread_mutex_lock(&mutex);

while(*p 《= 0)

{

pthread_cond_wait(&cond1,&mutex);//条件等待,条件不满足,陷入阻塞

}

printf(“i am workconsume :%d,i consume %d

”,(int)syscall(SYS_gettid),*p);

--(*p);

pthread_cond_signal(&cond2);//通知生产者

pthread_mutex_unlock(&mutex);//释放锁

}

return NULL;

}

int main()

{

pthread_t cons[NUMBER],prod[NUMBER];

pthread_mutex_init(&mutex,NULL);//互斥锁初始化

pthread_cond_init(&cond1,NULL);//条件变量初始化

pthread_cond_init(&cond2,NULL);//条件变量初始化

int i = 0;

for(;i 《 NUMBER;++i)

{

int ret = pthread_create(&prod[i],NULL,WorkProduct,(void*)&g_bowl);

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

ret = pthread_create(&cons[i],NULL,WorkConsume,(void*)&g_bowl);

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

}

for(i = 0;i 《 NUMBER;++i)

{

pthread_join(cons[i],NULL);//线程等待

pthread_join(prod[i],NULL);

}

pthread_mutex_destroy(&mutex);//销毁互斥锁

pthread_cond_destroy(&cond1);

pthread_cond_destroy(&cond2);

while(1)

{

printf(“i am main work thread

”);

sleep(1);

}

return 0;

}

在这里为什么有两个条件变量呢?

若所有的线程只使用一个条件变量,会导致所有线程最后都进入PCB等待队列。

thread apply all bt查看:

7.3.5情况分析:两个生产者,两个消费者,一个PCB等待队列

1、最开始的情况,两个消费者抢到了锁,此时生产者未生产,则都放入PCB等待队列中

2、一个生产者抢到了锁,生产了一份材料,唤醒一个消费者,此时三者抢锁,若两个生产者分别先后抢到了锁,则都进入PCB等待队列中

3、只有一个消费者,则必会抢到锁,消费材料,唤醒PCB等待队列,若此时唤醒的是,消费者,则现在是这样一个情况:

4、两个消费者在外边抢锁,一定都会进入PCB等待队列中

解决上述问题可采用两种方法:

1、使用int pthread_cond_broadcast(pthread_cond_t *cond);,唤醒PCB等待队列中所有的线程。此时所有线程都会同时执行抢锁逻辑,太消费资源了。此方法不妥

2、采用两个PCB等待序列,一个放生产者,一个放消费者,生产者唤醒消费者,消费者唤醒生产者。

8.线程取消

8.1线程取消函数接口

int pthread_cancel(pthread_t thread);

一个线程可以通过调用该函数向另一个线程发送取消请求。这不是个阻塞型接口, 发出请求后, 函数就立刻返回了, 而不会等待目标线程退出之后才返回。

调用pthread_cancel时, 会向目标线程发送一个SIGCANCEL的信号, 该信号就是kill -l中消失的32号信号。

线程的默认取消状态是PTHREAD_CANCEL_ENABLE。即是可被取消的。

什么是取消点?可通过man pthreads查看取消点

就是对于某些函数, 如果线程允许取消且取消类型是延迟取消, 并且线程也收到了取消请求, 那么当执行到这些函数的时候, 线程就可以退出了。

8.2线程取消带来的弊端

目标线程可能会持有互斥量、 信号量或其他类型的锁, 这时候如果收到取消请求, 并且取消类型是异步取消, 那么可能目标线程掌握的资源还没有来得及释放就被迫退出了, 这可能会给其他线程带来不可恢复的后果, 比如死锁(其他线程再也无法获得资源) 。

注意:

轻易不要调用pthread_cancel函数, 在外部杀死线程是很糟糕的做法,毕竟如果想通知目标线程退出, 还可以采取其他方法。

如果不得不允许线程取消, 那么在某些非常关键不容有失的代码区域, 暂时将线程设置成不可取消状态, 退出关键区域之后, 再恢复成可以取消的状态。

在非关键的区域, 也要将线程设置成延迟取消, 永远不要设置成异步取消。

8.2线程清理函数

假设遇到取消请求, 线程执行到了取消点, 却没有来得及做清理动作(如动态申请的内存没有释放, 申请的互斥量没有解锁等) , 可能会导致错误的产生, 比如死锁, 甚至是进程崩溃。

为了避免这种情况, 线程可以设置一个或多个清理函数, 线程取消或退出时,会自动执行这些清理函数, 以确保资源处于一致的状态。

如果线程被取消, 清理函数则会负责解锁操作。

void pthread_cleanup_push(void (*routine)(void *),void *arg);

void pthread_cleanup_pop(int execute);

这两个函数必须同时出现, 并且属于同一个语法块。

何时会触发注册的清理函数:?

1、当线程的主函数是调用pthread_exit返回的, 清理函数总是会被执行。

2、当线程是被其他线程调用pthread_cancel取消的, 清理函数总是会被执行。

3、当线程的主函数是通过return返回的, 并且pthread_cleanup_pop的唯一参数execute是0时, 清理函数不会被执行。

4、线程的主函数是通过return返回的, 并且pthread_cleanup_pop的唯一参数execute是非零值时, 清理函数会执行一次。

代码:

#include 《stdio.h》

#include 《stdlib.h》

#include 《time.h》

#include 《pthread.h》

#include 《unistd.h》

#include 《sys/syscall.h》

#define NUMBER 2

int g_bowl = 0;

pthread_mutex_t mutex;//定义互斥锁

void clean(void *arg)

{

printf(“Clean up:%s

”,(char*)arg);

pthread_mutex_unlock(&mutex);//释放锁

}

void *WorkCancel(void *arg)

{

pthread_mutex_lock(&mutex);

pthread_cleanup_push(clean,“clean up handler”);//清除函数的push

struct timespec t = {3,0};//取消点

nanosleep(&t,0);

pthread_cleanup_pop(0);//清除

pthread_mutex_unlock(&mutex);

}

void *WorkWhile(void *arg)

{

sleep(5);

pthread_mutex_lock(&mutex);

printf(“i get the mutex

”);//若能拿到资源,则表示取消清理函数成功!

pthread_mutex_unlock(&mutex);

return NULL;

}

int main()

{

pthread_t cons,prod;

pthread_mutex_init(&mutex,NULL);//互斥锁初始化

int ret = pthread_create(&prod,NULL,WorkCancel,(void*)&g_bowl);//该线程拿到锁,然后挂掉

if(ret != 0)

{

perror(“pthread_create”);

return -1;

}

int ret1 = pthread_create(&cons,NULL,WorkWhile,(void*)&ret);//测试该线程是否可以拿到锁

if(ret1 != 0)

{

perror(“pthread_create”);

return -1;

}

pthread_cancel(prod);//取消该线程

pthread_join(prod,NULL);//线程等待

pthread_join(cons,NULL);//线程等待

pthread_mutex_destroy(&mutex);//销毁互斥锁

while(1)

{

sleep(1);

}

return 0;

}

结果:只要拿到锁,就表明线程清理函数成功了。

9.多线程与fork()

永远不要在多线程程序里面调用fork。

Linux的fork函数, 会复制一个进程, 对于多线程程序而言, fork函数复制的是用fork的那个线程, 而并不复制其他的线程。fork之后其他线程都不见了。Linux存在forkall语义的系统调用, 无法做到将多线程全部复制。

多线程程序在fork之前, 其他线程可能正持有互斥量处理临界区的代码。fork之后, 其他线程都不见了, 那么互斥量的值可能处于不可用的状态, 也不会有其他线程来将互斥量解锁。

10.生产者与消费者模型

10.1生产者与消费者模型的本质

本质上是一个线程安全的队列,和两种角色的线程(生产者和消费者)

存在三种关系:

1、生产者与生产者互斥

2、消费者与消费者互斥

3、生产者与消费者同步+互斥

10.2为什么需要生产者与消费者模型?

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生成完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费解耦的。

10.3优点

1、解耦

2、支持高并发

3、支持忙闲不均

10.4实现两个消费者线程,两个生产者线程的生产者消费者模型

生产者生成时用的同一个全局变量,故对该全局变量进行了加锁。

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

#include 《pthread.h》

#include 《queue》

#include 《sys/syscall.h》

#define PTHREAD_COUNT 2

int data = 0;//全局变量作为插入数据

pthread_mutex_t mutex1;

class ModelOfConProd{

public:

ModelOfConProd()//构造

{

_capacity = 10;

pthread_mutex_init(&_mutex,NULL);

pthread_cond_init(&_cons,NULL);

pthread_cond_init(&_prod,NULL);

}

~ModelOfConProd()//析构

{

_capacity = 0;

pthread_mutex_destroy(&_mutex);

pthread_cond_destroy(&_cons);

pthread_cond_destroy(&_prod);

}

void Push(int data)//push数据,生产者线程使用的

{

pthread_mutex_lock(&_mutex);

while((int)_queue.size() 》= _capacity)

{

pthread_cond_wait(&_prod,&_mutex);

}

_queue.push(data);

pthread_mutex_unlock(&_mutex);

pthread_cond_signal(&_cons);

}

void Pop(int& data)//pop数据,消费者线程使用的

{

pthread_mutex_lock(&_mutex);

while(_queue.empty())

{

pthread_cond_wait(&_cons,&_mutex);

}

data = _queue.front();

_queue.pop();

pthread_mutex_unlock(&_mutex);

pthread_cond_signal(&_prod);

}

private:

int _capacity;//容量大小,限制容量大小

std::queue《int》 _queue;//队列

pthread_mutex_t _mutex;//互斥锁

pthread_cond_t _cons;//消费者条件变量

pthread_cond_t _prod;//生产者条件变量

};

void *ConsumerStart(void *arg)//消费者入口函数

{

ModelOfConProd *cp = (ModelOfConProd *)arg;

while(1)

{

cp-》Push(data);

printf(“i am pid : %d,i push :%d

”,(int)syscall(SYS_gettid),data);

pthread_mutex_lock(&mutex1);//++的时候,给该全局变量加锁

++data;

pthread_mutex_unlock(&mutex1);

}

}

void *ProductsStart(void *arg)//生产者入口函数

{

ModelOfConProd *cp = (ModelOfConProd *)arg;

int data = 0;

while(1)

{

cp-》Pop(data);

printf(“i am pid : %d,i pop :%d

”,(int)syscall(SYS_gettid),data);

}

}

int main()

{

ModelOfConProd *cp = new ModelOfConProd;

pthread_mutex_init(&mutex1,NULL);

pthread_t cons[PTHREAD_COUNT],prod[PTHREAD_COUNT];

for(int i = 0;i 《 PTHREAD_COUNT; ++i)

{

int ret = pthread_create(&cons[i],NULL,ConsumerStart,(void*)cp);

if(ret 《 0)

{

perror(“pthread_create”);

return -1;

}

ret = pthread_create(&prod[i],NULL,ProductsStart,(void*)cp);

if(ret 《 0)

{

perror(“pthread_create”);

return -1;

}

}

for(int i = 0;i 《 PTHREAD_COUNT;++i)

{

pthread_join(cons[i],NULL);

pthread_join(prod[i],NULL);

}

pthread_mutex_destroy(&mutex1);

return 0;

}

11.写多线程时应注意

先考虑代码的核心逻辑(先实现)

考虑核心逻辑中是否访问临界资源或者说执行临界区代码,如果有就需要保持互斥

考虑线程之间是否需要同步

编辑:jq

原文标题:多线程详解,一篇文章彻底搞懂多线程中各个难点

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
分享:

评论

相关推荐

MSK调制解调器的matlab仿真

继续讲解程序!MSK也能进行相干解调?是的!同样是采用锁相环!先来看看MSK的优点,这是由于下面的这....
的头像 通信工程师专辑 发表于 09-18 11:43 102次 阅读
MSK调制解调器的matlab仿真

介绍3种方法跨时钟域处理方法

跨时钟域处理是FPGA设计中经常遇到的问题,而如何处理好跨时钟域间的数据,可以说是每个FPGA初学者....
的头像 FPGA设计论坛 发表于 09-18 11:33 623次 阅读
介绍3种方法跨时钟域处理方法

机智云追踪外卖骑手保温箱硬件开发和项目演示

01 本章实现功能介绍 追踪外卖骑手的保温箱的GPS定位信息以及外卖箱是否被人打开,以防止骑手在送餐....
的头像 机智云开发者 发表于 09-18 11:03 103次 阅读

深入探究Linux系统噪音统计(osnoise tracer)

在Linux系统中作为一个普通线程是非常苦逼的。不仅NMI 、硬中断、软中断可以打断它,甚至其它普通....
的头像 Linux阅码场 发表于 09-18 10:53 143次 阅读
深入探究Linux系统噪音统计(osnoise tracer)

基于51单片机的温度传感器实验总结

51单片机有哪些特殊功能寄存器? 如何去编写基于51单片机的温度传感器代码? ...
发表于 09-18 07:06 0次 阅读

avr单片机INT0是如何去模拟代码的

avr单片机INT0是如何去模拟代码的?怎样去编写其代码?...
发表于 09-18 06:49 0次 阅读

有关独立看门狗和窗口看门狗的简单介绍

独立看门狗是什么?独立看门狗有哪些功能?独立看门狗有哪些相关寄存器? 窗口看门狗是什么?窗口看门狗有哪些相关寄存器?窗口...
发表于 09-18 06:42 0次 阅读

三菱交流伺服系统伺服故障和报警代码

伺服故障和报警代码大全,超实用!使用三菱交流伺服系统主要由三个系列:MR-ES、MR-J2S、MR-J3。通常故障情况可由伺服...
发表于 09-17 08:54 0次 阅读

计算机组成原理

计算机组成原理_第八章 (18页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积...
发表于 09-17 08:47 0次 阅读

硬盘使用时的常见故障

 一、硬盘使用时的常见故障   1.为自己的旧机器升级,挂上了新的大硬盘,而且主板却不认:  由于在较早的主板上存在着8.4G...
发表于 09-17 07:58 0次 阅读

空调器室内机基本电路原理介绍

一、空调电脑板各元器名称及作用格兰仕某款空调器室内机主板如下图所示科龙冷暖辅助电加热型空调器室内机主板如下图所示古桥冷暖...
发表于 09-17 07:09 0次 阅读

分享stm32之独立看门狗的相关知识

为什么要看门狗?看门狗解决的问题是什么? 怎样去计算独立看门狗的超时时间? 怎样去操作独立看门狗呢? ...
发表于 09-17 07:03 0次 阅读

如何利用51单片机制作从左至右再从右制作的流水灯呢

如何利用51单片机制作从左至右再从右制作的流水灯呢?如何编写其代码程序?...
发表于 09-17 06:52 0次 阅读

松下伺服报警代码保护功能

《松下伺服故障报警代码分析及处理》由会员分享,可在线阅读,更多相关《松下伺服故障报警代码分析及处理(2页珍藏版)》请在人人文...
发表于 09-17 06:20 0次 阅读

4800H已为过去时,游戏本CPU+GPU组合给你新选择

2021年转眼已过去三分之二,在这段时间里,《生化危机8》、《赛博朋克2077》等热门的游戏大作不断....
的头像 话说科技 发表于 09-16 13:14 106次 阅读
4800H已为过去时,游戏本CPU+GPU组合给你新选择

​开发板上玩GTA RISC-V多项移植项目成功运作中

电子发烧友网报道(文/周凯扬)RISC-V近期再度掀起了不小的热度,苹果招募RISC-V程序员负责其....
的头像 电子发烧友网 发表于 09-16 11:59 140次 阅读
​开发板上玩GTA RISC-V多项移植项目成功运作中

Arm 新技术助力汽车产业拥抱软件定义的未来

在车载原型设计和测试方面,高性能的AVA-AP1搭载80 核配置,提供更高的 CPU 性能和额外的 ....
发表于 09-16 11:52 961次 阅读
Arm 新技术助力汽车产业拥抱软件定义的未来

盘点主流步进电机主控芯片​内核硬,性能高

电子发烧友网报道(文/李宁远)说到电机主控芯片,市面上国内国外对应的产品都非常多,很难用好或者不好来....
的头像 电子发烧友网 发表于 09-16 10:38 136次 阅读
盘点主流步进电机主控芯片​内核硬,性能高

ADI公司发布集成精密库仑计数器的纳安级功耗原电池SoH监控器

ADI今天推出集成精密库仑计数器的纳安级功耗原电池(不可充电)健康状态(SoH)监控器LTC3337....
发表于 09-16 10:34 544次 阅读
ADI公司发布集成精密库仑计数器的纳安级功耗原电池SoH监控器

嵌入式开发中实用的宏打印函数

宏打印函数在我们的嵌入式开发中,使用printf打印一些信息是一种常用的调试手段。但是,在打印的信息....
的头像 FPGA之家 发表于 09-16 10:05 98次 阅读
嵌入式开发中实用的宏打印函数

使用Kotlin替代Java重构AOSP应用

两年前,Android 开源项目 (AOSP) 应用团队开始使用 Kotlin 替代 Java 重构....
的头像 谷歌开发者 发表于 09-16 09:26 101次 阅读
使用Kotlin替代Java重构AOSP应用

魔方网表,无代码开发平台NCDP的无冕之王

NCDP也就是No-code development platform,无代码开发平台,我第一次听到....
的头像 话说科技 发表于 09-15 14:34 85次 阅读

51单片机的启动文件作用是什么

在我们使用kei c51创建一个51单片机项目时,会有如下图所示的提示: 一般情况下,需要选择“是”....
的头像 嵌入式ARM 发表于 09-15 09:12 181次 阅读
51单片机的启动文件作用是什么

iPhone13mini上市时间 iPhone13mini参数

明天就是苹果秋季发布会了,此次iPhone13系列一共有四款机型,它们分别是iPhone13 min....
的头像 Les 发表于 09-14 14:36 2077次 阅读

国产高性能GPGPU的破局点在哪

电子发烧友网报道(文/吴子鹏)GPU诞生的原因是人们需要更好地处理3D游戏中的图形渲染,因此GPU拥....
的头像 电子发烧友网 发表于 09-14 09:58 168次 阅读

FastThreadLocal快在哪里

blog.csdn.net/mycs2012/article/details/90898128 1 ....
的头像 Android编程精选 发表于 09-13 09:17 132次 阅读

CPU上最流行的加速神经网络推理方法之一

量化是在 CPU 上最流行的加速神经网络推理方法之一。去年,TensorFlow Lite 通过 X....
的头像 TensorFlow 发表于 09-12 15:57 416次 阅读

C++基础语法友元类和友元函数

本期是C++基础语法分享的第五节,今天给大家来分享一下: (1)explicit(显式)关键字; (....
的头像 C语言编程学习基地 发表于 09-12 09:52 190次 阅读

一条SQL语句是怎么被执行的

一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一....
的头像 Linux爱好者 发表于 09-12 09:44 168次 阅读
一条SQL语句是怎么被执行的

计算机如何执行你写的代码

计算机如何执行你写的代码?知乎上有人提问:电脑怎样执行编程语言的? 很多刚刚入坑的小白可能对此完全没....
的头像 嵌入式ARM 发表于 09-12 09:33 256次 阅读
计算机如何执行你写的代码

KW37A大功率无线直放站的六大优势

昨天有人在后台留言问宝,直放站是个什么登西?好家伙,提出了好问题,相信大家也有同样的疑惑,那么今天就....
发表于 09-11 12:03 38次 阅读

如何选择合适的收银机主板

什么是收银机?其实收银机就相当是一台一体式电脑,内部结构有主板、CPU、内存,存储还有显示屏。双赞主....
发表于 09-11 11:44 73次 阅读

如何通过Python脚本实现WIFI密码的暴力破解

前言 本文将记录学习下如何通过 Python 脚本实现 WIFI 密码的暴力破解,从而实现免费蹭网。....
的头像 马哥Linux运维 发表于 09-10 17:09 323次 阅读
如何通过Python脚本实现WIFI密码的暴力破解

园区防爆气象站的功能和特点分别是什么

园区防爆气象站的功能和特点介绍FT-FB【风途】主要还是化工园区可能发生危害巨大的事故,影响颇大。同....
发表于 09-09 16:26 21次 阅读

苹果13和苹果12有什么区别

   最近关于iphone13手机的消息越来越多,也爆出了iphone13手机的外观概念图,那么ip....
的头像 汽车玩家 发表于 09-09 14:30 4881次 阅读

软件工程师为什么要写文档

在大多数软件工程师对编写、使用和维护代码的抱怨中,一个常见的问题是缺乏高质量的文档。缺乏文档有什么副....
的头像 Linux阅码场 发表于 09-09 11:26 251次 阅读

一个快速切入全新百亿美元市场的方法

不论是创业还是就业,首选需要选择一个赛道,因为一个天花板高的赛道可以给你更大的成长空间。如今有一个全....
的头像 电子发烧友网 发表于 09-09 10:58 1723次 阅读

不甘落后的欧盟终于在HPC上下血本

电子发烧友网报道(文/周凯扬)要说起超级计算机,多数人想到的都是日本的富岳、美国的Summit和中国....
的头像 电子发烧友网 发表于 09-09 10:54 285次 阅读

使用deepstream-test的范例代码修改车牌识别与遮盖

前一篇文章提到使用deepstream-test的范例代码,修改成“车牌识别”与“遮盖(redact....
的头像 NVIDIA英伟达企业解决方案 发表于 09-09 10:04 176次 阅读
使用deepstream-test的范例代码修改车牌识别与遮盖

在STM32G4片内不同存储空间运行的速度差异

最近有人问起程序在STM32G4片内不同存储空间运行的速度差异。说实在的,这个很难说死或说出个绝对的....
的头像 茶话MCU 发表于 09-09 09:57 235次 阅读
在STM32G4片内不同存储空间运行的速度差异

苹果13和12的区别在哪?

iPhone13与iPhone12的第一个区别就是价格,iPhone13刚上市,价格肯定不会太低,如....
的头像 倩倩 发表于 09-09 09:40 3341次 阅读

Imagination和浙江大学信息与电子工程学院宣布建立合作关系

Imagination Technologies公司副总裁、中国区总经理刘国军表示:“Imagina....
的头像 西西 发表于 09-09 09:33 2672次 阅读

骑手保温箱追踪及温湿度监测4G设备接入机智云教程

01 前言 利用机智云提供的通用版App即使不懂云和App开发,也可以在不用写任何代码的情况下,轻松....
的头像 机智云开发者 发表于 09-09 09:16 147次 阅读
骑手保温箱追踪及温湿度监测4G设备接入机智云教程

瑞萨电子推出32位RX671 MCU,实现高性能和高能效可支持非接触式HMI功能

RX671 MCU提供多种封装形式,引脚数从48至145不等,拥有高达2MB闪存和384KB SRA....
发表于 09-08 14:54 2198次 阅读
瑞萨电子推出32位RX671 MCU,实现高性能和高能效可支持非接触式HMI功能

这家RISC-V公司获字节跳动、联想、百度投资 加码高端CPU研发!

2021年9月8日,RISC-V公司睿思芯科完成数千万美金A轮融资,持续加码自主可控的高端CPU研发....
的头像 Carol Li 发表于 09-08 14:16 1553次 阅读
这家RISC-V公司获字节跳动、联想、百度投资 加码高端CPU研发!

Kitronik ARCADE游戏手柄实现连连看

连连看相信大家都玩过,但这个用Kitronik AR CADE游戏手柄来玩连连看的项目你相信是一个高....
的头像 电子森林 发表于 09-08 11:47 223次 阅读
Kitronik ARCADE游戏手柄实现连连看

自动ECO参考网表来自动修改实现网表

本次发布带来了如下几个重要的更新: 新增RTL Patch ECO,无需重新综合,节省了大量时间 直....
的头像 FPGA技术江湖 发表于 09-08 11:06 219次 阅读

如何把Docker Registry迁移到Harbor

“要如何将 docker registry 中的镜像迁移至 harbor?本文介绍了四种具体的思路和....
的头像 马哥Linux运维 发表于 09-07 16:29 300次 阅读
如何把Docker Registry迁移到Harbor

最为精简的一个Linux Fork炸弹解析

转自:http://blog.saymagic.cn/2015/03/25/fork-bomb.ht....
的头像 Linux爱好者 发表于 09-07 16:12 115次 阅读

Openwrt通用固件 make menuconfig配置教程

Openwrt通用固件 make menuconfig配置教程
发表于 09-07 12:01 29次 阅读

最新HBM3内存技术速率可达8.4Gbps

电子发烧友网报道(文/黄晶晶)近几年,数据中心、云服务以及物联网设备保持需求的高速增长。在大数据的处....
的头像 电子发烧友网 发表于 09-06 10:41 1265次 阅读

使用Intellij IDEA的一些小技巧

https://blog.csdn.net/linsongbin1/article/details/....
的头像 Android编程精选 发表于 09-05 15:03 318次 阅读

内联汇编代码中的关键语法规则讲解

一、基本 asm 格式 1. 语法规则 2. test1.c 插入空指令 3. test2.c 操作....
的头像 硬件攻城狮 发表于 09-05 09:46 227次 阅读

导热硅脂解决电脑CPU散热问题

CPU作为电脑的心脏,它的发热量是相当惊人的,也是不可忽视的。一般CPU通过导热硅脂材料将热量传递给....
发表于 09-04 14:27 182次 阅读

苹果新品供应链抢先看!传iPhone 13充电功率提升至33W

电子发烧友网报道 (文/莫婷婷、程文智)进入9月,关于苹果秋季发布会的消息越来越多。据爆料,今年iP....
的头像 电子发烧友网 发表于 09-03 14:22 505次 阅读

自适应计算加速软件定义硬件时代到来

以前设计一个产品,先要规划好硬件架构,等硬件设计全部完成后,才会开始软件部分的开发,然后才是完整产品....
的头像 电子发烧友网 发表于 09-03 11:20 698次 阅读

C语言代码中的extern

在你的C语言代码中,不知能否看到类似下面的代码: 这好像没有什么问题,你应该还会想:“嗯⋯是啊,我们....
的头像 STM32嵌入式开发 发表于 09-02 15:13 205次 阅读
C语言代码中的extern

你们知道指针和引用正确的使用场景吗

先解决两个疑问 ◆ 指针和引用的不同之处是什么? ◆ 何时用用指针?何时用引用? 指针和引用的不同之....
的头像 STM32嵌入式开发 发表于 09-02 14:37 180次 阅读
你们知道指针和引用正确的使用场景吗

KEIL 调试方法经验总结

我们已经了解了很多的 KEIL 调试方法,但是到底该怎么使用这些方法呢?这篇文章将介绍个人的调试经验....
的头像 FPGA之家 发表于 09-02 10:29 275次 阅读
KEIL 调试方法经验总结

SMV512K32-SP 16MB 防辐射 SRAM

SMV512K32是一款高性能异步CMOS SRAM,由32位524,288个字组成。可在两种模式:主控或受控间进行引脚选择。主设件为用户提供了定义的自主EDAC擦除选项。从器件选择采用按要求擦除特性,此特性可由一个主器件启动。根据用户需要,可提供3个读周期和4个写周期(描述如下)。 特性 20ns读取,13.8ns写入(最大存取时间) 与商用 512K x 32 SRAM器件功能兼容 内置EDAC(错误侦测和校正)以减轻软错误 用于自主校正的内置引擎 CMOS兼容输入和输出电平,3态双向数据总线 3.3±0.3VI /O,1.8±0.15V内核 辐射性能放射耐受性是一个基于最初器件标准的典型值。辐射数据和批量验收测试可用 - 细节请与厂家联系。 设计使用基底工程和抗辐射(HBD)与硅空间技术公司(SST)许可协议下的< sup> TM 技术和存储器设计。 TID抗扰度&gt; 3e5rad(Si) SER&lt; 5e-17翻转/位 - 天使用(CRPLE96来计算用于与地同步轨道,太阳安静期的SER。 LET = 110 MeV (T = 398K) 采用76引线陶瓷方形扁平封装 可提供工程评估(/EM)样品这些部件只用于工程评估。它们的加工工艺为非兼容流程(例如,无预烧过程等),...
发表于 01-08 17:47 338次 阅读
SMV512K32-SP 16MB 防辐射 SRAM

SN74HCT273A 具有清零功能的八路 D 类触发器

与其它产品相比 D 类触发器   Technology Family VCC (Min) (V) VCC (Max) (V) Rating Operating temperature range (C)   SN74HCT273A HCT     2     6     Catalog     -40 to 85    
发表于 01-08 17:46 291次 阅读
SN74HCT273A 具有清零功能的八路 D 类触发器

SN74HC273A 具有清零功能的八路 D 类触发器

与其它产品相比 D 类触发器   Technology Family VCC (Min) (V) VCC (Max) (V) Bits (#) Rating Operating temperature range (C)   SN74HC273A HC     2     6     8     Catalog     -40 to 85    
发表于 01-08 17:46 388次 阅读
SN74HC273A 具有清零功能的八路 D 类触发器

SN74ABT16373A 具有三态输出的 16 位透明 D 类锁存器

'ABT16373A是16位透明D型锁存器,具有3态输出,专为驱动高电容或相对低阻抗负载而设计。它们特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 这些器件可用作两个8位锁存器或一个16位锁存器。当锁存使能(LE)输入为高电平时,Q输出跟随数据(D)输入。当LE变为低电平时,Q输出锁存在D输入端设置的电平。 缓冲输出使能(OE \)输入可用于将8个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响锁存器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 当VCC介于0和2.1 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保2.1 V以上的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 SN54ABT16373A的特点是可在-55°C至125°C的整个军用温度范围内工作。 SN74ABT16373A的特点是在-40°C至85°C的温度范围内工作。 ...
发表于 10-11 15:07 293次 阅读
SN74ABT16373A 具有三态输出的 16 位透明 D 类锁存器

SN74ALVCH16820 具有双路输出和三态输出的 3.3V 10 位触发器

这个10位触发器设计用于1.65 V至3.6 VVCC操作。 < p> SN74ALVCH16820的触发器是边沿触发的D型触发器。在时钟(CLK)输入的正跳变时,器件在Q输出端提供真实数据。 缓冲输出使能(OE)输入可用于将10个输出放入正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \输入不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 为确保上电或断电期间的高阻态,OE \应连接到VCC通过上拉电阻;电阻的最小值由驱动器的电流吸收能力决定。 提供有源总线保持电路,用于将未使用或未驱动的输入保持在有效的逻辑电平。不建议在上拉电路中使用上拉或下拉电阻。 特性 德州仪器广播公司的成员?系列 数据输入端的总线保持消除了对外部上拉/下拉电阻的需求 每个JESD的闩锁性能超过250 mA 17 ESD保护超过JESD 22 2000-V人体模型(...
发表于 10-11 14:49 90次 阅读
SN74ALVCH16820 具有双路输出和三态输出的 3.3V 10 位触发器

SN74ABT16374A 具有三态输出的 16 位边沿 D 类触发器

'ABT16374A是16位边沿触发D型触发器,具有3态输出,专为驱动高电容或相对低阻抗而设计负载。它们特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 这些器件可用作两个8位触发器或一个16位触发器。在时钟(CLK)输入的正跳变时,触发器的Q输出采用在数据(D)输入处设置的逻辑电平。 缓冲输出使能(OE \)输入可用于将8个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 当VCC介于0和2.1 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保2.1 V以上的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 SN54ABT16374A的特点是可在-55°C至125°C的整个军用温度范围内工作。 SN74ABT16374A的特点是在-40°C至85°C的温度范围内工作。 特性 ...
发表于 10-11 11:46 153次 阅读
SN74ABT16374A 具有三态输出的 16 位边沿 D 类触发器

SN74AHCT16374 具有三态输出的 16 位边沿 D 类触发器

'AHCT16374器件是16位边沿触发D型触发器,具有3态输出,专为驱动高电容或相对较低的电容而设计阻抗负载。它们特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 这些器件可用作两个8位触发器或一个16位触发器。在时钟(CLK)输入的正跳变时,触发器的Q输出取数据(D)输入的逻辑电平。 缓冲输出使能(OE \)输入可用于将8个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 为了确保上电或断电期间的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 SN54AHCT16374的特点是可在-55°C至125°C的整个军用温度范围内工作。 SN74AHCT16374的工作温度范围为-40°C至85°C。   特性 德州仪器WidebusTM家庭成员 EPICTM(...
发表于 10-11 11:32 191次 阅读
SN74AHCT16374 具有三态输出的 16 位边沿 D 类触发器

CY74FCT162374T 具有三态输出的 16 位边沿触发 D 类触发器

CY74FCT16374T和CY74FCT162374T是16位D型寄存器,设计用作高速,低功耗总线应用中的缓冲寄存器。通过连接输出使能(OE)和时钟(CLK)输入,这些器件可用作两个独立的8位寄存器或单个16位寄存器。流通式引脚排列和小型收缩包装有助于简化电路板布局。 使用Ioff为部分断电应用完全指定此设备。 Ioff电路禁用输出,防止在断电时损坏通过器件的电流回流。 CY74FCT16374T非常适合驱动高电容负载和低阻抗背板。 CY74FCT162374T具有24 mA平衡输出驱动器,输出端带有限流电阻。这减少了对外部终端电阻的需求,并提供最小的下冲和减少的接地反弹。 CY74FCT162374T非常适合驱动传输线。 特性 Ioff支持部分省电模式操作 边沿速率控制电路用于显着改善的噪声特性 典型的输出偏斜< 250 ps ESD&gt; 2000V TSSOP(19.6密耳间距)和SSOP(25密耳间距)封装 工业温度范围-40°C至+ 85°C VCC= 5V±10% CY74FCT16374T特点: ...
发表于 10-11 11:28 317次 阅读
CY74FCT162374T 具有三态输出的 16 位边沿触发 D 类触发器

SN74ALVCH16260 具有三态输出的 12 位至 24 位多路复用 D 类锁存器

这个12位至24位多路复用D型锁存器设计用于1.65 V至3.6 VVCC操作。 SN74ALVCH16260用于必须将两个独立数据路径复用到单个数据路径或从单个数据路径解复用的应用中。典型应用包括在微处理器或总线接口应用中复用和/或解复用地址和数据信息。该器件在存储器交错应用中也很有用。 三个12位I /O端口(A1-A12,1B1-1B12和2B1-2B12)可用于地址和/或数据传输。输出使能(OE1B \,OE2B \和OEA \)输入控制总线收发器功能。 OE1B \和OE2B \控制信号还允许在A到B方向上进行存储体控制。 可以使用内部存储锁存器存储地址和/或数据信息。锁存使能(LE1B,LE2B,LEA1B和LEA2B)输入用于控制数据存储。当锁存使能输入为高电平时,锁存器是透明的。当锁存使能输入变为低电平时,输入端的数据被锁存并保持锁存,直到锁存使能输入返回高电平为止。 确保上电或断电期间的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 < p> SN74ALVCH16260的工...
发表于 10-11 11:08 73次 阅读
SN74ALVCH16260 具有三态输出的 12 位至 24 位多路复用 D 类锁存器

SN74ALVCH16374 具有三态输出的 16 位边沿 D 类触发器

这个16位边沿触发D型触发器设计用于1.65 V至3.6 VVCC操作。 SN74ALVCH16374特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。它可以用作两个8位触发器或一个16位触发器。在时钟(CLK)输入的正跳变时,触发器的Q输出取数据(D)输入的逻辑电平。 OE \可用于将8个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 为确保上电或断电期间的高阻态,OE \应连接到VCC通过上拉电阻;电阻的最小值由驱动器的电流吸收能力决定。 有源总线保持电路将未使用或未驱动的输入保持在有效的逻辑状态。不建议在上拉电路中使用上拉或下拉电阻。 特性 德州仪器广播公司的成员?系列 工作电压范围为1.65至3.6 V 最大tpd为4.2 ns,3.3 V ±24-mA输出驱动在3.3 V 数据输入...
发表于 10-11 11:06 105次 阅读
SN74ALVCH16374 具有三态输出的 16 位边沿 D 类触发器

SN74ALVCH16373 具有三态输出的 16 位透明 D 类锁存器

这个16位透明D型锁存器设计用于1.65 V至3.6 VVCC操作。 SN74ALVCH16373特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。该器件可用作两个8位锁存器或一个16位锁存器。当锁存使能(LE)输入为高电平时,Q输出跟随数据(D)输入。当LE变为低电平时,Q输出锁存在D输入设置的电平。 缓冲输出使能(OE)输入可用于将8个输出置于正常状态逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响锁存器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 为确保上电或断电期间的高阻态,OE \应连接到VCC通过上拉电阻;电阻的最小值由驱动器的电流吸收能力决定。 有源总线保持电路将未使用或未驱动的输入保持在有效的逻辑状态。不建议在上拉电路中使用上拉或下拉电阻。 特性 德州仪器广播公司的成员?系列 工作电压范围为1.65 V至3.6 V 最大tpd3.6 ns,3.3 V ...
发表于 10-11 11:02 202次 阅读
SN74ALVCH16373 具有三态输出的 16 位透明 D 类锁存器

SN74LVCH16373A 具有三态输出的 16 位透明 D 类锁存器

这个16位透明D型锁存器设计用于1.65 V至3.6 VVCC操作。 特性 德州仪器宽带总线系列成员 典型VOLP(输出接地反弹) &lt; 0.8 V,VCC= 3.3 V,TA= 25°C 典型VOHV(输出V < sub> OH Undershoot) &gt; 2 V在VCC= 3.3 V,TA= 25°C Ioff支持实时插入,部分 - 电源关闭模式和后驱动保护 支持混合模式信号操作(具有3.3VVCC的5V输入和输出电压) < li>数据输入端的总线保持消除了对外部上拉或下拉电阻的需求 每个JESD的闩锁性能超过250 mA 17 ESD保护超过JESD 22 < ul> 2000-V人体模型(A114-A) 200-V机型(A115-A) 参数 与其它产品相比 D 类锁存器   Technology Family VCC (Min) (V) VCC (Max) (V) Bits (#) ...
发表于 10-11 11:00 327次 阅读
SN74LVCH16373A 具有三态输出的 16 位透明 D 类锁存器

SN74ABTH16260 具有三态输出的 12 位至 24 位多路复用 D 类锁存器

SN54ABT16260和SN74ABTH16260是12位至24位多路复用D型锁存器,用于必须复用两条独立数据路径的应用中,或者从单个数据路径中解复用。典型应用包括在微处理器或总线接口应用中复用和/或解复用地址和数据信息。该器件在存储器交错应用中也很有用。 三个12位I /O端口(A1-A12,1B1-1B12和2B1-2B12)可用于地址和/或数据传输。输出使能(OE1B \,OE2B \和OEA \)输入控制总线收发器功能。 OE1B \和OE2B \控制信号还允许A-to-B方向的存储体控制。 可以使用内部存储锁存器存储地址和/或数据信息。锁存使能(LE1B,LE2B,LEA1B和LEA2B)输入用于控制数据存储。当锁存使能输入为高电平时,锁存器是透明的。当锁存使能输入变为低电平时,输入端的数据被锁存并保持锁存状态,直到锁存使能输入返回高电平为止。 当VCC介于0和2.1 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保2.1 V以上的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 ...
发表于 10-11 10:51 106次 阅读
SN74ABTH16260 具有三态输出的 12 位至 24 位多路复用 D 类锁存器

SN74ABT162823A 具有三态输出的 18 位总线接口触发器

这些18位总线接口触发器具有3态输出,专为驱动高电容或相对低阻抗负载而设计。它们特别适用于实现更宽的缓冲寄存器,I /O端口,带奇偶校验的双向总线驱动器和工作寄存器。 ?? ABT162823A器件可用作两个9位触发器或一个18位触发器。当时钟使能(CLKEN)\输入为低电平时,D型触发器在时钟的低到高转换时输入数据。将CLKEN \置为高电平会禁用时钟缓冲器,从而锁存输出。将清零(CLR)\输入设为低电平会使Q输出变为低电平而与时钟无关。 缓冲输出使能(OE)\输入将9个输出置于正常逻辑状态(高电平)或低电平)或高阻抗状态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动器提供了驱动总线线路的能力,无需接口或上拉组件。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 输出设计为源电流或吸收电流高达12 mA,包括等效的25- 串联电阻,用于减少过冲和下冲。 这些器件完全符合热插拔规定使用Ioff和上电3状态的应用程序。 Ioff电路禁用输出,防止在断电时损坏通过器件的电流回流。上电和断电期间,上电三态电路将输出置...
发表于 10-11 10:48 80次 阅读
SN74ABT162823A 具有三态输出的 18 位总线接口触发器

SN74ABTH162260 具有串联阻尼电阻和三态输出的 12 位到 24 位多路复用 D 类锁存器

'ABTH162260是12位至24位多路复用D型锁存器,用于两个独立数据路径必须复用或复用的应用中。 ,单一数据路径。典型应用包括在微处理器或总线接口应用中复用和/或解复用地址和数据信息。这些器件在存储器交错应用中也很有用。 三个12位I /O端口(A1-A12,1B1-1B12和2B1-2B12)可用于地址和/或数据传输。输出使能(OE1B \,OE2B \和OEA \)输入控制总线收发器功能。 OE1B \和OE2B \控制信号还允许A-to-B方向的存储体控制。 可以使用内部存储锁存器存储地址和/或数据信息。锁存使能(LE1B,LE2B,LEA1B和LEA2B)输入用于控制数据存储。当锁存使能输入为高电平时,锁存器是透明的。当锁存使能输入变为低电平时,输入端的数据被锁存并保持锁存状态,直到锁存使能输入返回高电平为止。 B端口输出设计为吸收高达12 mA的电流,包括等效的25系列电阻,以减少过冲和下冲。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 当VCC介于0和2.1 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保2.1 V以上的高阻态,OE \应通过...
发表于 10-11 10:45 116次 阅读
SN74ABTH162260 具有串联阻尼电阻和三态输出的 12 位到 24 位多路复用 D 类锁存器

SN74ABT162841 具有三态输出的 20 位总线接口 D 类锁存器

这些20位透明D型锁存器具有同相三态输出,专为驱动高电容或相对低阻抗负载而设计。它们特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 ?? ABT162841器件可用作两个10位锁存器或一个20位锁存器。锁存使能(1LE或2LE)输入为高电平时,相应的10位锁存器的Q输出跟随数据(D)输入。当LE变为低电平时,Q输出锁存在D输入设置的电平。 缓冲输出使能(10E或2OE)输入可用于放置输出。相应的10位锁存器处于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。 输出设计为吸收高达12 mA的电流,包括等效的25- 用于减少过冲和下冲的串联电阻。 这些器件完全适用于使用I的热插入应用关闭并启动3状态。 Ioff电路禁用输出,防止在断电时损坏通过器件的电流回流。上电和断电期间,上电三态电路将输出置于高阻态,从而防止驱动器冲突。 为确保上电或断电期间的高阻态, OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 OE \不影响锁存器的内部操作。当输出处于高阻态时,可以保留旧数据...
发表于 10-11 10:43 190次 阅读
SN74ABT162841 具有三态输出的 20 位总线接口 D 类锁存器

SN74ALVTH16821 具有三态输出的 2.5V/3.3V 20 位总线接口触发器

'ALVTH16821器件是20位总线接口触发器,具有3态输出,设计用于2.5 V或3.3 VVCC操作,但能够为5 V系统环境提供TTL接口。 这些器件可用作两个10位触发器或一个20位触发器。 20位触发器是边沿触发的D型触发器。在时钟(CLK)的正跳变时,触发器存储在D输入端设置的逻辑电平。 缓冲输出使能(OE \)输入可用于将10个输出置于正常逻辑状态(高电平或低电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 当VCC介于0和1.2 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保1.2 V以上的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 SN54ALVTH16821的特点是可在-55°C至125°C的整个军用温度范围内工作。 SN74ALVTH16821的工作温度范围为-40&de...
发表于 10-11 10:35 66次 阅读
SN74ALVTH16821 具有三态输出的 2.5V/3.3V 20 位总线接口触发器

SN74ALVTH16374 具有三态输出的 2.5V/3.3V 16 位边沿 D 类触发器

'ALVTH16374器件是16位边沿触发D型触发器,具有3态输出,设计用于2.5V或3.3VV < sub> CC 操作,但能够为5 V系统环境提供TTL接口。这些器件特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 这些器件可用作两个8位触发器或一个16位翻转器。翻牌。在时钟(CLK)的正跳变时,触发器存储在数据(D)输入处设置的逻辑电平。 缓冲输出使能(OE)输入可用于将8个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE不影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 /p> 当VCC介于0和1.2 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保1.2 V以上的高阻态,OE应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 SN54ALVTH16374的特点是在-55°C至125°C的整个军用温度...
发表于 10-11 10:31 79次 阅读
SN74ALVTH16374 具有三态输出的 2.5V/3.3V 16 位边沿 D 类触发器

SN74ABTH16823 具有三态输出的 18 位总线接口触发器

这些18位触发器具有3态输出,专为驱动高电容或相对低阻抗负载而设计。它们特别适用于实现更宽的缓冲寄存器,I /O端口,带奇偶校验的双向总线驱动器和工作寄存器。 'ABTH16823可用作两个9位触发器或一个18位触发器。当时钟使能(CLKEN \)输入为低电平时,D型触发器在时钟的低到高转换时输入数据。将CLKEN \置为高电平会禁用时钟缓冲器,锁存输出。将清零(CLR \)输入置为低电平会使Q输出变为低电平,与时钟无关。 缓冲输出使能(OE \)输入可用于将9个输出置于正常逻辑状态(高或低逻辑电平)或高阻态。在高阻抗状态下,输出既不会加载也不会显着驱动总线。高阻抗状态和增加的驱动提供了驱动总线的能力,而无需接口或上拉组件。 OE \不会影响触发器的内部操作。当输出处于高阻态时,可以保留旧数据或输入新数据。 当VCC介于0和2.1 V之间时,器件在上电或断电期间处于高阻态。但是,为了确保2.1 V以上的高阻态,OE \应通过上拉电阻连接到VCC;电阻的最小值由驱动器的电流吸收能力决定。 提供有源总线保持电路,用于保持有效逻辑电平的未使用或浮动数据输入。 ...
发表于 10-10 17:15 166次 阅读
SN74ABTH16823 具有三态输出的 18 位总线接口触发器

SN74AHCT16373 具有三态输出的 16 位透明 D 类锁存器

SNxAHCT16373器件是16位透明D型锁存器,具有3态输出,专为驱动高电容或相对低阻抗负载而设计。它们特别适用于实现缓冲寄存器,I /O端口,双向总线驱动器和工作寄存器。 特性 德州仪器Widebus™系列的成员 EPIC™(增强型高性能注入CMOS)工艺 输入兼容TTL电压 分布式VCC和GND引脚最大限度地提高高速 开关噪声 流通式架构优化PCB布局 每个JESD的闩锁性能超过250 mA 17 ESD保护每个MIL-STD超过2000 V- 883, 方法3015;使用机器型号超过200 V(C = 200 pF,R = 0) 封装选项包括: 塑料收缩小外形(DL)封装 < li>薄收缩小外形(DGG)封装 薄超小外形(DGV)封装 80-mil精细间距陶瓷扁平(WD)封装 25密耳的中心间距 参数 与其它产品相比 D 类锁存器   ...
发表于 10-10 16:23 144次 阅读
SN74AHCT16373 具有三态输出的 16 位透明 D 类锁存器