自动秒收录

c 线程 (c线程间通信的几种方法)


文章编号:18832 / 分类:Midjourney绘画 / 更新时间:2024-07-23 17:58:30 / 浏览:
c

线程间通信(IPC)是操作系统中一个非常重要的概念。它允许多个线程在同一进程中进行交互和数据共享。在 C 语言中,有几种方法可以实现线程间通信,包括:

1. 共享内存

共享内存是最直接的 IPC 方法。它涉及创建一块内存区域,多个线程都可以访问和修改它。每个线程都可以直接读写共享内存中的数据,从而实现通信。共享内存是一种高效且直接的方法,但如果不仔细同步,可能会导致竞争条件和数据损坏。

要创建共享内存,可以使用以下步骤:

c include include include int main() {// 创建共享内存段int shared_memory = (int )mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (shared_memory == MAP_FAILED) {perror("mmap");exit(1);}// 初始化共享内存shared_memory = 0;// 等待其他线程访问共享内存while (shared_memory == 0);// 获取其他线程写入的值int value = shared_memory;// 输出值printf("Value received from other thread: %d\n", value);return 0; }

2. 信号量

信号量是一种同步原语,用于控制对共享资源的访问。它是一个整数变量,表示资源的可用性。线程可以使用信号量来获取(等待)或释放(信号)资源。当一个线程获取一个信号量时,它将等待,直到信号量变为非零。当一个线程释放一个信号量时,它将使信号量增加 1。信号量可以防止多个线程同时访问共享资源,从而避免竞争条件。

要创建信号量,可以使用以下步骤:

c include include include int main() {// 创建信号量sem_t semaphore;int result = sem_init(&semaphore, 0, 0);if (result != 0) {perror("sem_init");exit(1);}// 等待并获取信号量sem_wait(&semaphore);// 访问共享资源// 释放信号量sem_post(&semaphore);return 0; }

3. 管道

管道是一种单向通信机制,它允许一个线程向另一个线程发送数据。管道由一个读端和一个写端组成。写线程可以向管道中写入数据,而读线程可以从管道中读取数据。管道通常用于父子进程之间的通信,也可以用于线程之间的通信。

要创建管道,可以使用以下步骤:

c include include include int main() {// 创建管道int pipe_fds[2];int result = pipe(pipe_fds);if (result == -1) {perror("pipe");exit(1);}// 写入管道const char message = "Hello world";write(pipe_fds[1], message, strlen(message) + 1);// 读取管道char buffer[100];read(pipe_fds[0], buffer, sizeof(buffer));// 输出从管道读取的值printf("Value received from other thread: %s\n", buffer);return 0; }

4. 消息队列

消息队列是一种基于消息的通信机制,它允许线程在队列中交换消息。每个消息由一个类型和一个数据区域组成。线程可以使用消息队列将消息发送到队列,也可以从队列中接收消息。消息队列可以用于应用程序之间的通信,也可以用于线程之间的通信。

要创建消息队列,可以使用以下步骤:

c include include include int main() {// 创建消息队列int message_queue_id = msgget(IPC_PRIVATE, 0666);if (message_queue_id == -1) {perror("msgget");exit(1);}// 发送消息到队列struct msgbuf {long mtype;char mtext[100];};struct msgbuf message;message.mtype = 1;strcpy(message.mtext, "Hello world");msgsnd(message_queue_id, &message, sizeof(message.mtext), 0);// 接收消息从队列struct msgbuf received_message;msgrcv(message_queue_id, &received_message, sizeof(received_message.mtext), 1, 0);// 输出从队列接收的值printf("Value received from other thread: %s\n", received_message.mtext);return 0; }

5. 套接字

套接字是一种低级通信机制,它允许在同一计算机或不同计算机上的两个进程之间交换数据。套接字可以用于应用程序之间的通信,也可以用于线程之间的通信。套接字连接的两个端点称为客户端和服务器。

要创建套接字并进行通信,可以使用以下步骤:

c include include include include int main() {// 创建服务器套接字int server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1) {perror("socket");exit(1);}// 绑定服务器套接字到地址和端口struct sockaddr_in server_address;server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = INADDR_ANY;server_address.sin_port = htons(8080);int bind_result = bind(server_socket, (struct sockaddr )&server_address, sizeof(server_address));if (bind_result == -1) {perror("bind");exit(1);}// 监听服务器套接字int listen_result = listen(server_socket, SOMAXCONN);if (listen_result == -1) {perror("listen");exit(1);}// 创建客户端套接字int client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket == -1) {perror("socket");exit(1);}// 连接客户端套接字到服务器套接字struct sockaddr_in client_address;client_address.sin_family = AF_INET;client_address.sin_addr.s_addr = inet_addr("127.0.0.1");client_address.sin_port = htons(8080);int connect_result = connect(client_socket, (struct sockaddr )&client_address, sizeof(client_address));if (connect_result == -1) {perror("connect");exit(1);}// 发送数据到服务器套接字const char message = "Hello world";send(client_socket, message, strlen(message) + 1, 0);// 接收数据从服务器套接字char buffer[100];recv(client_socket, buffer, sizeof(buffer), 0);// 输出从服务器套接字接收的值printf("Value received from other thread: %s\n", buffer);return 0; }

结论

线程间通信在多线程程序中至关重要。C 语言提供了多种方法来实现线程间通信,包括共享内存、信号量、管道、消息队列和套接字。根据应用程序的特定要求,选择最合适的 IPC 机制非常重要。共享内存提供了最直接的通信方式,但需要小心避免竞争条件。信号量可以防止竞争条件,但引入了额外的开销。管道和消息队列提供了更灵活的通信机制


线程的几种控制方式以及线程间的几种通信方式

几种进程间的通信方式(1) 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。 进程的血缘关系通常指父子进程关系。 (2)有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。 (3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。 因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 (4)消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。 消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。 (5)信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。 (6)共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。 它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 (7)套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同及其间的进程通信。 几种线程间的通信机制1、锁机制1.1 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。 1.2 读写锁:允许多个线程同时读共享数据,而对写操作互斥。 1.3 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。 对条件测试是在互斥锁的保护下进行的。 条件变量始终与互斥锁一起使用。 2、信号量机制:包括无名线程信号量与有名线程信号量3、信号机制:类似于进程间的信号处理。 线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。

C#socket异步怎么实现 线程间通信如何实现

基于C#的socket编程的tcp异步实现一、摘要本篇博文阐述基于TCP通信协议的异步实现。 二、实验平台Visual Studio 2010三、异步通信实现原理及常用方法3.1 建立连接 在同步模式中,在服务器上使用Accept方法接入连接请求,而在客户端则使用Connect方法来连接服务器。 相对地,在异步模式下,服务器可以使用BeginAccept方法和EndAccept方法来完成连接到客户端的任务,在客户端则通过BeginConnect方法和EndConnect方法来实现与服务器的连接。 BeginAccept在异步方式下传入的连接尝试,它允许其他动作而不必等待连接建立才继续执行后面程序。 在调用BeginAccept之前,必须使用Listen方法来侦听是否有连接请求,BeginAccept的函数原型为:BeginAccept(AsyncCallback AsyncCallback, Ojbect state)参数:AsyncCallBack:代表回调函数state:表示状态信息,必须保证state中包含socket的句柄使用BeginAccept的基本流程是:(1)创建本地终节点,并新建套接字与本地终节点进行绑定;(2)在端口上侦听是否有新的连接请求;(3)请求开始接入新的连接,传入Socket的实例或者StateOjbect的实例。 参考代码:复制代码//定义IP地址IPAddress local = (127.0,0,1);IPEndPoint iep = new IPEndPoint(local,);//创建服务器的socket对象Socket server = new Socket(,,);(iep);(20);(new AsyncCallback(Accept),server);复制代码当BeginAccept()方法调用结束后,一旦新的连接发生,将调用回调函数,而该回调函数必须包括用来结束接入连接操作的EndAccept()方法。 该方法参数列表为 Socket EndAccept(IAsyncResult iar)下面为回调函数的实例:复制代码void Accept(IAsyncResult iar){//还原传入的原始套接字Socket MyServer = (Socket);//在原始套接字上调用EndAccept方法,返回新的套接字Socket service = (iar);}复制代码至此,服务器端已经准备好了。 客户端应通过BeginConnect方法和EndConnect来远程连接主机。 在调用BeginConnect方法时必须注册相应的回调函数并且至少传递一个Socket的实例给state参数,以保证EndConnect方法中能使用原始的套接字。 下面是一段是BeginConnect的调用:Socket socket=new Socket(,,)IPAddress ip=(127.0.0.1);IPEndPoint iep=new IPEndPoint(ip,);(iep, new AsyncCallback(Connect),socket);EndConnect是一种阻塞方法,用于完成BeginConnect方法的异步连接诶远程主机的请求。 在注册了回调函数后必须接收BeginConnect方法返回的IASynccReuslt作为参数。 下面为代码展示:复制代码void Connect(IAsyncResult iar){Socket client=(Socket);try{(iar);}catch (Exception e){(());}finally{}}复制代码除了采用上述方法建立连接之后,也可以采用TcpListener类里面的方法进行连接建立。 下面是服务器端对关于TcpListener类使用BeginAccetpTcpClient方法处理一个传入的连接尝试。 以下是使用BeginAccetpTcpClient方法和EndAccetpTcpClient方法的代码:复制代码public static void DoBeginAccept(TcpListener listner){//开始从客户端监听连接(Waitting for a connection);//接收连接//开始准备接入新的连接,一旦有新连接尝试则调用回调函数(new AsyncCallback(DoAcceptTcpCliet), listner);}//处理客户端的连接public static void DoAcceptTcpCliet(IAsyncResult iar){//还原原始的TcpListner对象TcpListener listener = (TcpListener);//完成连接的动作,并返回新的TcpClientTcpClient client = (iar);(连接成功);}复制代码代码的处理逻辑为:(1)调用BeginAccetpTcpClient方法开开始连接新的连接,当连接视图发生时,回调函数被调用以完成连接操作;(2)上面DoAcceptTcpCliet方法通过AsyncState属性获得由BeginAcceptTcpClient传入的listner实例;(3)在得到listener对象后,用它调用EndAcceptTcpClient方法,该方法返回新的包含客户端信息的TcpClient。 BeginConnect方法和EndConnect方法可用于客户端尝试建立与服务端的连接,这里和第一种方法并无区别。 下面看实例:复制代码public void doBeginConnect(IAsyncResult iar){Socket client=(Socket);//开始与远程主机进行连接(serverIP[0],,requestCallBack,client);(开始与服务器进行连接);}private void requestCallBack(IAsyncResult iar){try{//还原原始的TcpClient对象TcpClient client=(TcpClient);//(iar);(与服务器{0}连接成功,);}catch(Exception e){(());}finally{}}复制代码以上是建立连接的两种方法。 可根据需要选择使用。 3.2 发送与接受数据在建立了套接字的连接后,就可以服务器端和客户端之间进行数据通信了。 异步套接字用BeginSend和EndSend方法来负责数据的发送。 注意在调用BeginSend方法前要确保双方都已经建立连接,否则会出异常。 下面展示代码:复制代码private static void Send(Socket handler, String data){// Convert the string data to byte data using ASCII encoding. byte[] byteData = (data);// Begin sending the data to the remote device. (byteData, 0, , 0, new AsyncCallback(SendCallback), handler);}private static void SendCallback(IAsyncResult ar){try{// Retrieve the socket from the state object. Socket handler = (Socket);// Complete sending the data to the remote device. int bytesSent = (ar);(Sent {0} bytes to client., bytesSent);();();}catch (Exception e){(());}}复制代码接收数据是通过BeginReceive和EndReceive方法:复制代码private static void Receive(Socket client){try{// Create the state object. StateObject state = new StateObject(); = client;// Begin receiving the data from the remote device. (, 0, , 0, new AsyncCallback(ReceiveCallback), state);}catch (Exception e){(());}}private static void ReceiveCallback(IAsyncResult ar){try{// Retrieve the state object and the client socket // from the asynchronous state object. StateObject state = (StateObject);Socket client = ;// Read data from the remote device. int bytesRead = (ar);if (bytesRead > 0){// There might be more data, so store the data received so far. ((, 0, bytesRead));// Get the rest of the data. (, 0, , 0, new AsyncCallback(ReceiveCallback), state);}else{// All the data has arrived; put it in response. if ( > 1){response = ();}// Signal that all bytes have been received. ();}}catch (Exception e){(());}}复制代码上述代码的处理逻辑为:(1)首先处理连接的回调函数里得到的通讯套接字client,接着开始接收数据;(2)当数据发送到缓冲区中,BeginReceive方法试图从buffer数组中读取长度为的数据块,并返回接收到的数据量bytesRead。 最后接收并打印数据。 除了上述方法外,还可以使用基于NetworkStream相关的异步发送和接收方法,下面是基于NetworkStream相关的异步发送和接收方法的使用介绍。 NetworkStream使用BeginRead和EndRead方法进行读操作,使用BeginWreite和EndWrete方法进行写操作,下面看实例:复制代码static void DataHandle(TcpClient client){TcpClient tcpClient = client;//使用TcpClient的GetStream方法获取网络流NetworkStream ns = ();//检查网络流是否可读if(){//定义缓冲区byte[] read = new byte[1024];(read,0,,new AsyncCallback(myReadCallBack),ns);}else{(无法从网络中读取流数据);}}public static void myReadCallBack(IAsyncResult iar){NetworkStream ns = (NetworkStream);byte[] read = new byte[1024];String data = ;int recv;recv = (iar);data = (data, (read, 0, recv));//接收到的消息长度可能大于缓冲区总大小,反复循环直到读完为止while (){(read, 0, , new AsyncCallback(myReadCallBack), ns);}//打印(您收到的信息是 + data);}复制代码3.3 程序阻塞与异步中的同步问题里提供了EventWaitHandle类来表示一个线程的同步事件。 EventWaitHandle即事件等待句柄,他允许线程通过操作系统互发信号和等待彼此的信号来达到线程同步的目的。 这个类有2个子类,分别为AutoRestEevnt(自动重置)和ManualRestEvent(手动重置)。 下面是线程同步的几个方法:(1)Rset方法:将事件状态设为非终止状态,导致线程阻塞。 这里的线程阻塞是指允许其他需要等待的线程进行阻塞即让含WaitOne()方法的线程阻塞;(2)Set方法:将事件状态设为终止状态,允许一个或多个等待线程继续。 该方法发送一个信号给操作系统,让处于等待的某个线程从阻塞状态转换为继续运行,即WaitOne方法的线程不在阻塞;(3)WaitOne方法:阻塞当前线程,直到当前的等待句柄收到信号。 此方法将一直使本线程处于阻塞状态直到收到信号为止,即当其他非阻塞进程调用set方法时可以继续执行。 复制代码public static void StartListening(){// Data buffer for incoming data. byte[] bytes = new Byte[1024];// Establish the local endpoint for the socket. // The DNS name of the computer // running the listener is . //IPHostEntry ipHostInfo = (());//IPAddress ipAddress = [0];IPAddress ipAddress = (127.0.0.1);IPEndPoint localEndPoint = new IPEndPoint(ipAddress, );// Create a TCP/IP socket. Socket listener = new Socket(,, );// Bind the socket to the local //endpoint and listen for incoming connections. try{(localEndPoint);(100);while (true){// Set the event to nonsignaled state. ();// Start an asynchronous socket to listen for connections. (Waiting for a connection...);(new AsyncCallback(AcceptCallback),listener);// Wait until a connection is made before continuing. ();}}catch (Exception e){(());}(\nPress ENTER to continue...);();}复制代码上述代码的逻辑为:(1)试用了ManualRestEvent对象创建一个等待句柄,在调用BeginAccept方法前使用Rest方法允许其他线程阻塞;(2)为了防止在连接完成之前对套接字进行读写操作,务必要在BeginAccept方法后调用WaitOne来让线程进入阻塞状态。 当有连接接入后系统会自动调用会调用回调函数,所以当代码执行到回调函数时说明连接已经成功,并在函数的第一句就调用Set方法让处于等待的线程可以继续执行。

线程间的通信方式

线程间的通信方式:简单讲就是在锁、堆里的对象包括普通对象个原子变量,他们之间可以实现线程间的通信。 1、通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。 线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。 线程A和B必须获得指向一个MySignal共享实例的引用,以便进行通信。 如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号。 需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。 2、忙等待(Busy Wait) 准备处理数据的线程B正在等待数据变为可用。 换句话说,它在等待线程A的一个信号,这个信号使hasDataToProcess()返回true,线程B运行在一个循环里,以等待这个信号。 3、wait(),notify()和notifyAll() 忙等待没有对运行等待线程的CPU进行有效的利用,除非平均等待时间非常短。 否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号。 Java有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。 类定义了三个方法,wait()、notify()和notifyAll()来实现这个等待机制。 一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。 为了调用wait()或者notify(),线程必须先获得那个对象的锁。 也就是说,线程必须在同步块里调用wait()或者notify()。 以下是MySingal的修改版本——使用了wait()和notify()的 等待线程将调用doWait(),而唤醒线程将调用doNotify()。 当一个线程调用一个对象的notify()方法,正在等待该对象的所有线程中将有一个线程被唤醒并允许执行(校注:这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程)。 同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。 如你所见,不管是等待线程还是唤醒线程都在同步块里调用wait()和notify()。 这是强制性的!一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。 否则,会抛出IllegalMonitorStateException异常。 但是,这怎么可能?等待线程在同步块里面执行的时候,不是一直持有监视器对象(myMonitor对象)的锁吗?等待线程不能阻塞唤醒线程进入doNotify()的同步块吗?答案是:的确不能。 一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。 这将允许其他线程也可以调用wait()或者notify()。 一旦一个线程被唤醒,不能立刻就退出wait()的方法调用,直到调用notify()的线程退出了它自己的同步块。 换句话说:被唤醒的线程必须重新获得监视器对象的锁,才可以退出wait()的方法调用,因为wait方法调用运行在同步块里面。 如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。 4、丢失的信号(Missed Signals) notify()和notifyAll()方法不会保存调用它们的方法,因为当这两个方法被调用时,有可能没有线程处于等待状态。 通知信号过后便丢弃了。 因此,如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。 这可能是也可能不是个问题。 不过,在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。 为了避免丢失信号,必须把它们保存在信号类里。 在MyWaitNotify的例子中,通知信号应被存储在MyWaitNotify实例的一个成员变量里。 留意doNotify()方法在调用notify()前把wasSignalled变量设为true。 同时,留意doWait()方法在调用wait()前会检查wasSignalled变量。 事实上,如果没有信号在前一次doWait()调用和这次doWait()调用之间的时间段里被接收到,它将只调用wait()。 (校注:为了避免信号丢失, 用一个变量来保存是否被通知过。 在notify前,设置自己已经被通知过。 在wait后,设置自己没有被通知过,需要等待通知。 ) 5、假唤醒 由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。 这就是所谓的假唤醒(spurious wakeups)。 无端端地醒过来了。 如果在MyWaitNotify2的doWait()方法里发生了假唤醒,等待线程即使没有收到正确的信号,也能够执行后续的操作。 这可能导致你的应用程序出现严重问题。 为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。 这样的一个while循环叫做自旋锁 (校注:这种做法要慎重,目前的JVM实现自旋会消耗CPU,如果长时间不调用doNotify方法,doWait方法会一直自旋,CPU会消耗太大) 。 被唤醒的线程会自旋直到自旋锁(while循环)里的条件变为false。 留意wait()方法是在while循环里,而不在if表达式里。 如果等待线程没有收到信号就唤醒,wasSignalled变量将变为false,while循环会再执行一次,促使醒来的线程回到等待状态。 6、多个线程等待相同信号 如果你有多个线程在等待,被notifyAll()唤醒,但只有一个被允许继续执行,使用while循环也是个好方法。 每次只有一个线程可以获得监视器对象锁,意味着只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)。 一旦这个线程退出doWait()的同步块,其他线程退出wait()调用,并在while循环里检查wasSignalled变量值。 但是,这个标志已经被第一个唤醒的线程清除了,所以其余醒来的线程将回到等待状态,直到下次信号到来。


相关标签: c线程c线程间通信的几种方法

本文地址:https://www.badfl.com/article/c4dcf7661baf1ace18ce.html

上一篇:bulkingbulkingup...
下一篇:androidsdktoolsandroid是什么...

发表评论

温馨提示

做上本站友情链接,在您站上点击一次,即可自动收录并自动排在本站第一位!
<a href="https://www.badfl.com/" target="_blank">自动秒收录</a>