Linux多线程机制

一,硬件层面:

缓存一致性 和 锁定机制。

1. 缓存一致性(Cache Coherence)

确保所有处理器看到的共享内存内容是一致的,即使这些数据被缓存在了不同处理器的高速缓存中。

2. 锁定机制(Locking Mechanisms)

目的:确保在多线程或多处理器环境下,对共享资源的访问是互斥的,避免数据竞争和不一致性。

形式:总线锁定、锁定指令、原子操作指令

二,原子性基础

原子性是并发控制和数据同步的基础,无论是在硬件层面还是软件层面,都是确保程序正确性、一致性和可靠性的关键要素。

以下是一些线程同步机制与原子操作的关系:

互斥锁(Mutex):虽然互斥锁的逻辑比单纯的原子操作复杂,但它在内部会使用原子操作(如CAS)来安全地改变锁的状态,确保同一时间只有一个线程可以持有锁。

信号量(Semaphore):信号量的实现可能包含原子的递增和递减操作来管理资源计数,这些操作需要是原子的,以避免计数错误。

条件变量(Condition Variable):条件变量通常与互斥锁一起使用,其核心操作(如等待和唤醒)虽然不是直接基于原子操作,但在配合同步互斥锁时,会间接利用到原子操作来保护状态变更。

原子变量(Atomic Variables):直接基于原子操作,如C++中的std::atomic,可以实现简单的线程同步,如计数器的原子增减。

读写锁(Read-Write Lock):读写锁的升级和降级操作可能依赖原子操作来保证状态的正确转换。

屏障(Barrier):在某些实现中,屏障的同步点可能涉及原子地更新计数器或状态标志。

三,系统软件层面

在软件层面,Linux系统中实现多线程同步的机制主要依赖于POSIX线程库(pthreads),以下是一些核心的同步原语和机制:

1,通俗解释:


互斥锁(Mutex)

想象你家有个厕所,互斥锁就像厕所门上的锁。一个人进去后锁上门,其他人就得在外面等着,直到里面的人出来并打开锁。在电脑里,这就是一个线程在使用资源时,其他线程要等待。

信号量(Semaphore)

假设你家有两辆自行车,信号量就像是自行车停车场的管理员。管理员有两把钥匙,代表两个资源。如果两辆车都被借走了,管理员就没钥匙给别人,别人得等着。一旦有人还车,管理员就多一把钥匙可以给下一个等待的人。在编程中,信号量可以管理多个资源,允许一定数量的线程同时访问。

条件变量(Condition Variable)

这像是在餐厅等位,服务员告诉你没位置,让你先坐在等候区。条件变量就是这个“等候区”,线程在某个条件不满足时,会在这里等待,直到条件满足(比如有空位了),服务员(其他线程)才会通知你(线程)可以开始了。

读写锁(Read-Write Lock)

像图书馆的书,很多人可以同时阅读(读锁),但一次只能有一个人修改(写锁)。这样,读者不影响彼此,但写者需要独占,以避免混乱。

自旋锁(Spin Lock)

自旋锁好比你去敲朋友家的门,如果门锁着,你不离开,而是在门口转圈(自旋)等待,直到门开了你才进去。在电脑里,线程也会这样不停尝试获取锁,直到成功为止,适合等待时间很短的情况。

原子操作

原子操作就像是瞬间完成的小魔术,比如瞬间从左手变出硬币到右手,中间过程你看不到。在编程中,这意味着操作快速且不可打断,保证了数据的安全性。

屏障(Barrier)

就像赛跑的起跑线,所有选手(线程)都准备好,等到发令枪响(屏障点),大家才一起出发。在编程中,屏障确保所有线程都到达某个点后,再一起继续执行下一步。

2,正经解释:

互斥锁(Mutex)

描述:最基本的同步机制,用于保护临界区,确保一次只有一个线程可以进入。当一个线程获得锁后,其他试图获取该锁的线程将会阻塞,直到锁被释放。

API示例:pthread_mutex_init(), pthread_mutex_lock(), pthread_mutex_unlock()。

信号量(Semaphore)

描述:信号量可以用于控制对资源的访问数量。它维护一个计数器,线程可以减少计数器来获取资源使用权,完成后增加计数器释放资源。信号量可以实现更复杂的同步逻辑,比如允许一定数量的线程同时访问资源。

API示例:sem_init(), sem_wait(), sem_post()。

条件变量(Condition Variable)

描述:条件变量用于线程间的同步,允许一个线程等待某个条件变为真,而另一个线程在条件满足时通知等待的线程。通常与互斥锁一起使用,以防止条件检查和通知之间的竞态条件。

API示例:pthread_cond_init(), pthread_cond_wait(), pthread_cond_signal(), pthread_cond_broadcast()。

读写锁(Read-Write Lock)

描述:读写锁允许多个读取者同时访问共享资源,但只允许一个写入者。这在读操作远多于写操作的场景下非常有用,可以提高并发性能。

API示例:pthread_rwlock_init(), pthread_rwlock_rdlock(), pthread_rwlock_wrlock(), pthread_rwlock_unlock()。

自旋锁(Spin Lock)

描述:一种简单的锁机制,当锁不可用时,线程不会立即睡眠,而是不断循环检查(自旋),直到锁变为可用状态。适合锁持有时间很短的场景,避免了线程上下文切换的开销。

API示例:pthread_spin_init(), pthread_spin_lock(), pthread_spin_unlock()。

原子操作

描述:通过特殊的编译器指令或硬件指令实现,保证操作的不可分割性,无需显式加锁。常用在轻量级同步和无锁编程中。

API示例:GCC内建函数如__atomic_fetch_add(),或C++11标准库中的std::atomic。

屏障(Barrier)

描述:一种同步点,所有线程必须都到达这个点后,所有线程才能继续执行。常用于线程间的同步,确保所有线程完成特定阶段后才开始下一阶段。

API示例:pthread_barrier_init(), pthread_barrier_wait()。

四,C++线程同步

在C++中,线程间的通信主要依赖于C++11标准引入的线程库以及一些底层操作系统提供的同步原语。以下是C++中常用的线程通信方法:

互斥锁(std::mutex):

用于保护共享资源,确保同一时间只有一个线程可以访问。通过lock()和unlock()操作来控制访问。

条件变量(std::condition_variable):

结合互斥锁使用,允许一个线程等待特定条件满足,而另一个线程在条件满足时通过notify_one()或notify_all()唤醒等待的线程。

原子操作(std::atomic):

提供基本类型的原子操作,如递增、递减、比较并交换(CAS)等,可以在无锁的情况下实现线程间的简单同步和通信。

读写锁(std::shared_mutex 或 std::shared_timed_mutex):

允许多个读取者同时访问共享资源,但只允许一个写入者。适用于读多写少的场景。

信号量(通过第三方库或操作系统API):

虽然C++标准库没有直接提供信号量,但可以通过POSIX线程库(如sem_t)或其他第三方库来实现,用于控制对资源的访问数量。

线程局部存储(thread_local):

每个线程都有自己独立的存储,用于保存线程私有数据,虽然主要用于隔离而不是通信,但在某些场景下可间接用于线程间的信息传递。

消息队列或管道(通过操作系统API):

对于更复杂的数据交换,可以使用操作系统提供的消息队列或管道机制,但这通常超出了标准C++库的范畴,需要使用如POSIX消息队列或管道API。

Future和Promise(std::future 和 std::promise):

用于异步任务的结果传递,一个线程(通常是工作线程)通过promise设置结果,另一个线程(通常是主线程)通过future获取结果,实现线程间的异步通信。

五,C++进程间通信

1. 管道(Pipe)

类型:匿名管道(适用于父子进程间)和命名管道(FIFO,适用于任意两个进程)。

C++实现:虽然标准库不直接支持,但可以使用C语言的pipe()函数创建匿名管道,以及mkfifo()创建命名管道,并通过文件描述符进行读写操作。

2. 信号(Signal)

用途:用于进程间简单事件的通知,如终止进程、挂起进程等。

C++实现:使用<csignal>头文件,通过signal()函数注册信号处理器。但请注意,信号处理在C++中应谨慎使用,因为它可能影响程序的正常执行流程。

3. 共享内存(Shared Memory)

特点:速度快,多个进程可以直接读写同一块内存区域。

C++实现:使用POSIX共享内存API,如shm_open(), mmap()等函数来创建和映射共享内存段。C++17引入了std::shared_memory_object和相关类作为更高层次的抽象,但并非所有编译器和平台都支持。

4. 信号量(Semaphore)

用途:控制多个进程对共享资源的访问。

C++实现:使用POSIX semaphore API,如sem_open(), sem_wait(), sem_post()等函数。

5. 消息队列(Message Queue)

特点:提供了一种存储和检索消息的方式,适用于需要记录消息的场景。

C++实现:使用POSIX消息队列API,如mq_open(), mq_send(), mq_receive()等函数。

6. 套接字(Socket)

用途:不仅限于网络通信,本地套接字(UNIX域套接字)也可以用于进程间通信。

C++实现:使用C++标准库中的<sys/socket.h>和相关网络编程接口,或者Boost.Asio库来简化套接字编程。

```python
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # We "pool" the model by simply taking the hidden state corresponding
        # to the first token.
        first_token_tensor = hidden_states[:, 0]
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output
from transformers.models.bert.configuration_bert import *
import torch
config = BertConfig.from_pretrained("bert-base-uncased")
bert_pooler = BertPooler(config=config)
print("input to bert pooler size: {}".format(config.hidden_size))
batch_size = 1
seq_len = 2
hidden_size = 768
x = torch.rand(batch_size, seq_len, hidden_size)
y = bert_pooler(x)
print(y.size())
```

  • 30
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值