`
jubincn
  • 浏览: 242566 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
文章分类
社区版块
存档分类
最新评论

6.087 Practical Programming in C, lec13

 
阅读更多

Multithreaded programming. Socketsand asynchronous I/O

Multithreaded programming

• OS implements scheduler – determines which threads execute

• Scheduling may execute threads in arbitrary order

• Without proper synchronization, code can executenon-deterministically

• Suppose we have two threads: 1 reads a variable, 2 modifiesthat variable

• Scheduler may execute 1, then 2, or 2 then 1

• Non-determinism creates a race condition – where thebehavior/result depends on the order of execution

线程是由操作系统来控制的,而不是程序自身。尽管进程也是由操作系统控制,在执行的时候会中断,但因为进程有自己的内存空间,因此一般不会发生线程中共享资源访问的问题。Racecondition是指结果受执行顺序影响的情况。

Race conditions in assembly

Consider the following program race.c:

unsigned int cnt =0;

void ∗count (void ∗arg){/∗thread body∗/

int i;

for ( i = 0 ; i < 100000000; i ++)

cnt++;

return NULL;

}

int main(void){

pthread_t tids[4];

int i;

for (i = 0; i < 4; i++)

pthread_create(&tids[i], NULL, count, NULL);

for(i = 0; i < 4; i++)

pthread_join(tids[i], NULL);

printf("cnt=%u\n", cnt);

return 0;

}

What is the value of cnt?

[Bryant and O’Halloran. Computer Systems: A Programmer’sPerspective. Prentice Hall, 2003.]

Ideally, should increment cnt 4 × 100000000 times, so cnt =400000000. However, running our code gives:

athena% ./race.o

cnt=137131900

athena% ./race.o

cnt=163688698

athena% ./race.o

cnt=169695163

So, what happened?

• C not designed for multithreading

• No notion of atomic operations in C

• Increment cnt++; maps to three assembly operations:

1. load cnt into a register

2. increment value in register

3. save new register value as new cnt

• So what happens if thread interrupted in the middle?

• Race condition!

上述程序结果令人吃惊,一个重要原因是我本以为++是原子操作,现在发现++并不是原子操作,看来要真正理解程序,还是要学编译原理啊。如何解决这个问题?很简单,加锁使其成为原子操作皆可。加锁看起来是一种简便的方式,随程序的复杂,锁也带来了很多问题,尤其是手动来控制加锁、解锁的过程,更容易出错。上面也提到,问题的一个重要根源就是C在设计时并没有考虑多线程的问题。而Java等变成语言提供了synchronized关键字等设施来自动地解决这个问题,大大减轻了程序员的负担。

Let’s fix our code:

pthread_mutex_t mutex;

unsigned int cnt = 0;

void ∗count (void ∗arg){/∗thread body∗/

int i;

for(i = 0; i < 100000000; i++){

pthread_mutex_lock(&mutex);

cnt++;

pthread_mutex_unlock (&mutex ) ;

}

return NULL;

}

int main ( void ) {

pthread_t tids[4];

int i;

pthread_mutex_init(&mutex, NULL);

for (i = 0; i < 4; i++)

pthread_create(&tids[i], NULL, count, NULL);

for(i = 0; i < 4; i++)

pthread_join(tids[i], NULL);

pthread_mutex_dest roy (&mutex ) ;

printf("cnt=%u\n", cnt);

return 0;

}

如上面的代码所示,对读写公共数据的操作加锁,即可解决这个问题。当然,加解锁自身必须是原子操作,否则加解锁的过程也是racecondition

Race conditions

• Note that new code functions correctly, but is much slower

• C statements not atomic–threads may be interrupted atassembly level, in the middle of a C statement

• Atomic operations like mutex locking must be specified asatomic using special assembly instructions

• Ensure that all statements accessing/modifying sharedvariables are synchronized

我觉得速度变慢的原因主要在线程切换上,加解锁的过程是原子操作,不会费很多时间。C中的statement不具备原子性是很多多线程程序出问题的一个重要原因,我想应该有人发明一种线程安全的C变种吧。要在读写共享变量的statement里面使用同步机制,这个在java里面是强制要求的,否则会报警。

Semaphores

• Semaphore – special nonnegative integer variable s,initially 1, which implements two atomic operations:

P(s)– wait until s > 0, decrement s and return

V(s)– increment s by 1, unblocking a waiting thread

• Mutex – locking calls P(s) and unlocking calls V(s)

• Implemented in <semaphore.h>, part of library rt, notpthread

Semaphore特别适合生产者 –消费者模型,生产者可以一直调用V(s)来生产数据,消费者则由P(s)控制,只有当计数大于0即有“产品”时,才可以运行进行消费。

Using semaphores

• Initialize semaphore to value:

int sem_init(sem_t ∗sem,int pshared, unsigned int value);

• Destroy semaphore:

int sem_destroy(sem_t ∗sem);

• Wait to lock, blocking:

int sem_wait(sem_t ∗sem);

• Try to lock, returning immediately (0 if now locked, −1otherwise):

int sem_trywait(sem_t ∗sem);

• Increment semaphore, unblocking a waiting thread:

int sem_post(sem_t ∗sem);

Producer and consumer revisited

• Use a semaphore to track available slots in shared buffer

• Use a semaphore to track items in shared buffer

• Use a semaphore/mutex to make buffer operations synchronous


使用semaphore,生产者–消费者模型显得更为清晰,说实话,一直没有看明白前面使用锁来实现的生产者—消费者模型,但这里就很容易看懂了。使用mutex是为了实现操作原子化,而slotitem锁的顺序是为了使生产和消费交替进行。

Thread safety

• Function is thread safe if it always behaves correctly whencalled from multiple concurrent threads

• Unsafe functions fal in several categories:

• accesses/modifies unsynchronized shared variables

functionsthat maintain state using static variables – like rand(),strtok()

functionsthat return pointers to static memory – like gethostbyname()

functionsthat call unsafe functions may be unsafe

记得在学习Java时一个重要的概念就是线程安全,听到这个词就会有如释重负的感觉。其实线程安全的方法比较好识别,只要自身不使用共享数据,然后调用的程序也都线程安全就好了。从中可以看出,线程安全像GPL,有很强的“传递性”,因此一个好的做法是将这些涉及到共享数据操作的部分封装起来,一个经典的方法就是数据库了。

Reentrant functions

• Reentrant function – does not reference any shared datawhen used by multiple threads

• All reentrant functions are thread-safe (are all thread-safefunctions reentrant?)

• Reentrant versions of many unsafe C standard library

functions exist:

Unsafe function Reentrant version

rand()rand_r()

strtok()strtok_r()

asctime()asctime_r()

ctime()ctime_r()

gethostbyaddr()gethostbyaddr_r()

gethostbyname()gethostbyname_r()

inet_ntoa()(none)

localtime()localtime_r()

随技术的不断发展,很多之前没有为线程考虑的类库都有了线程安全的方法,因此在进行程序设计时要首先考虑线程安全的方法。我想不是所有的线程安全方法都是reentrant方法,例如那些含有只读数据的方法应该也是线程安全的。还有一些方法通过内部访问机制也可以实现数据安全,如原子化操作等。

Thread safety

To make your code thread-safe:

• Use synchronization objects around shared variables

• Use reentrant functions

• Use synchronization around functions returning pointers toshared memory (lock-and-copy):

1. lock mutex for function

2. call unsafe function

3. dynamically allocate memory for result; (deep) copy result into newmemory

4. unlock mutex

Deadlock

• Defeating deadlock extremely difficult in general

• When using only mutexes, can use the “mutex lock orderingrule” to avoid deadlock scenarios:

A program is deadlock-free if, foreach pair of mutexes (s, t) in the program, each thread that usesboth s and t simultaneously locks them in the same order.

Sockets

• Socket – abstraction toenable communication across a network in a manner similar to fileI/O

• Uses header <sys/socket.h>(extension of C standard library)

• Network I/O, due to latency,usually implemented asynchronously, using multithreading

• Sockets use client/servermodel of establishing connections

网络通信是线程的一个重要应用领域。网络传输是不稳定的和昂贵的,因此需要通过异步来优化程序,多线程是实现这种异步的最佳选择。网络传输是很复杂的,涉及到很多协议,为了实现封装和隔离,CUNIX将其抽象为文件,通过文件描述符来代表网络链接,使网络通信成为本地文件操作。

Creating asocket

• Create a socket, getting thefile descriptor for that socket:

intsocket(int domain, int type, int protocol );

• domain– use constant AF_INET, so we’re using the internet; might alsouse AF_INET6 for IPv6 addresses

• type– use constant SOCK_STREAM for connection-based protocols likeTCP/IP; use SOCK_DGRAM for connectionless datagram protocols likeUDP (we’ll concentrate on the

former)

• protocol– specify 0 to use default protocol for the socket type (e.g. TCP)

• returnsnonnegative integer for file descriptor, or −1 if couldn’tcreate socket

• Don’t forget to close thefile descriptor when you’re done!

返回值为fd,此时这个socket就可以作为一个本地文件来操作了,尽管文件的内容可能在通过网络连接的另外一台机器上。

Connecting to aserver

• Using created socket, weconnect to server using:

int connect(int fd , structsockaddr ∗addr, int addr_len);

• fd – the socket’s filedescriptor

• addr – the address and portof the server to connect to; for

internet addresses, cast data oftype struct

sockaddr_in, which has thefollowing members:

• sin_family – addressfamily; always AF_INET

• sin_port – port in networkbyte order (use htons() to

convert to network byte order)

• sin_addr.s_addr – IPaddress in network byte order (use

htonl() to convert to networkbyte order)

• addr_len – size ofsockaddr_in structure

• returns 0 if successful

Associate serversocket with a port

• Using created socket, we bindto the port using:

intbind(int fd , struct sockaddr ∗addr, int addr_len);

• fd,addr, addr_len – same as for connect()

• notethat address should be IP address of desired interface (e.g. eth0)on local machine

• ensurethat port for server is not taken (or you may get “address alreadyin use” errors)

• return0 if socket successfully bound to port

Listening forclients

• Using the bound socket, startlistening:

intlisten ( int fd , int backlog);

• fd– bound socket file descriptor

• backlog– length of queue for pending TCP/IP connections; normally set toa large number, like 1024

• returns0 if successful

Accepting aclient’s connection

• Wait for a client’sconnection request (may already be queued):

intaccept(int fd , struct sockaddr ∗addr, int ∗addr_len);

• fd– socket’s file descriptor

• addr– pointer to structure to be filled with client address info (canbe NULL)

• addr_len– pointer to int that specifies length of structure pointed to byaddr; on output, specifies the length of the stored address (storedaddress may be truncated if bigger than supplied structure)

• returns(nonnegative) file descriptor for connected client socket ifsuccessful

Reading andwriting with sockets

• Send data using the followingfunctions:

intwrite ( int fd , const void ∗buf, size_t len );

intsend(int fd , const void ∗buf, size_t len, int flags );

• Receive data using thefollowing functions:

intread(int fd , void ∗buf, size_t len );

intrecv(int fd , void ∗buf, size_t len, int flags );

• fd– socket’s file descriptor

• buf– buffer of data to read or write

• len– length of buffer in bytes

• flags– special flags; we’ll just use 0

• allthese return the number of bytes read/written (if successful)

这里的fd应该就是accept中返回的fd

Asynchronous I/O

• Up to now, all I/O has beensynchronous – functions do not return until operation has beenperformed

• Multithreading allows us toread/write a file or socket without blocking our main program code(just put I/O functions in a separate thread)

• Multiplexed I/O – useselect() or poll() with multiple file descriptors

I/O multiplexingwith select()

• To check if multiplefiles/sockets have data to read/write/etc: (include <sys/select.h>)

intselect ( int nfds, fd_set ∗readfds, fd_set ∗writefds, fd_set ∗errorfds, struct timeval ∗timeout);

• nfds– specifies the total range of file descriptors to be tested (0up to nfds−1)

• readfds,writefds, errorfds – if not NULL, pointer to set of filedescriptors to be tested for being ready to read, write, or havingan error; on output, set will contain a list of only those filedescriptors that are ready

• timeout– if no file descriptors are ready immediately, maximum time towait for a file descriptor to be ready

• returnsthe total number of set file descriptor bits in all the sets

• Note that select() is ablocking function

• fd_set – a mask for filedescriptors; bits are set (“1”) if in the set, or unset (“0”)otherwise

• Use the following functionsto set up the structure:

FD_ZERO(&fdset) – initialize the set to have bits unset for all file descriptors

FD_SET(fd,&fdset) – set the bit for file descriptor fd in the set

FD_CLR(fd,&fdset) – clear the bit for file descriptor fd in the set

FD_ISSET(fd,&fdset) – returns nonzero if bit for file descriptor fd isset in the set

select像是个霸道的总管,每次检查是否有通信时,不仅阻塞通信,而且要检查所有的通信,实在是厉害的很,可惜不是很灵活。

I/O multiplexingusing poll()

• Similar to select(), butspecifies file descriptors differently: (include <poll.h>)

intpoll (struct pollfd fds [], nfds_t nfds, int timeout);

• fds– an array of pollfd structures, whose members fd, events, andrevents, are the file descriptor, events to check (OR-edcombination of flags like POLLIN, POLLOUT, POLLERR, POLLHUP), andresult of polling with that file descriptor for those events,respectively

• nfds– number of structures in the array

• timeout– number of milliseconds to wait; use 0 to return immediately, or−1 to block indefinitely

相比selectpoll就灵活的多,可以检查指定的socket



分享到:
评论

相关推荐

    Umich反应工程_1-25课件.zip

    lec13.ppt lec14.ppt lec15.ppt lec16.ppt lec17.ppt lec18.ppt lec19.ppt lec2.ppt lec20.ppt lec21.ppt lec22.ppt lec23.ppt lec24.ppt lec25.ppt lec3.ppt lec4.ppt lec5.ppt lec6.ppt lec7.ppt lec8.ppt lec9....

    MIT10_626S11_lec06.pdf

    In the previous lecture, we leant about impedance spectroscopy. Electrochemical impedance spectroscopy is the technique where the cell or electrode impedance is platted versus frequency. Thus, the ...

    programming in computing 10a lec2 ppt

    programming in computing 10a lec2

    lec.rar_LEC

    6. **划分风险等级**:根据LEC分值将危险源分为不同的风险等级,如低风险、中风险和高风险。 总的来说,"lec.rar_LEC"提供的工具是进行LEC风险评估的重要助手,它使用MATLAB语言编写,能够客观地计算和分析工作场所...

    EI374 高级算法-全套 PPT 课件-笔记

    EI374 高级算法-全套 PPT 课件-笔记 lec1-slides.pdf lec1.pdf lec2-slides.pdf lec2.pdf lec3-slides.pdf lec3.pdf lec4-slides.pdf ...lec6.pdf lec7.pdf lec8.pdf lec9.pdf lec10.pdf lec11.pdf

    MIT计算机图形学课程6.837课件

    Lec 13 Ray Tracing Lec 14 Acceleration Structures for Ray Casting Assignment 3 Lec 15 Shading and Material Appearance Lec 16 Texture Mapping and Shaders Lec 17 Sampling, Aliasing, and Mipmaps Lec ...

    Lec13 Greedy.pdf

    - **示例**:考虑活动选择问题中的子问题\( S_{ij} = \{a_k \in S: f_i \leq s_k ),即所有在活动\( a_i \)结束后开始且在活动\( a_j \)开始前结束的活动。如果存在一个最优解包含了活动\( a_i \),那么在\( a_i \)...

    lec-培训(完整版).pdf

    《LEC培训(完整版).pdf》是一份关于逻辑等效检查的详细教程,重点介绍了使用Conformal工具进行逻辑等效验证的方法和技术。Conformal是一款强大的逻辑等效检查工具,广泛应用于芯片设计的验证阶段,确保设计的逻辑...

    数字逻辑设计及应用教学英文课件:Lec13-Chap 6有作业.ppt

    数字逻辑设计及应用教学英文课件:Lec13-Chap 6有作业.ppt

    lec.rar_V2

    在Linux系统中,头文件是C语言编程的关键部分,它们允许程序员在源代码(如lec.c)中使用特定的函数和数据类型,而无需在每个文件中包含完整的实现。lec.c很可能是这个LAN Emulation客户端的实现文件,包含实际的...

    EI338 计算机系统工程-Computer Systems Engineering-全套 PPT 课件

    EI338 计算机系统工程-Computer Systems Engineering-全套 PPT 课件 CA-lec1.pdf ...lec6-OS.pdf lec7-OS.pdf lec8-OS.pdf lec9-OS.pdf lec10-OS.pdf lec11-OS.pdf lec12-OS.pdf Study-Guide.pdf Summary.pdf

    麻省理工matlab课件-MIT6_094IAP10_lec04.pdf

    麻省理工matlab课件-MIT6_094IAP10_lec04.pdf 本帖最后由 sunchy11 于 2012-2-8 15:46 编辑 分享个MIT的matlab 教程,属于初级入门,希望对大家有帮助哈。

    HOLLiAS-LEC G3 PLC选型手册.pdf

    由于提供的文件内容主要是一些网站链接、电子邮箱地址和数字的排列,没有提供实际的HOLLiAS-LEC G3 PLC选型手册的详细内容,我无法直接从中提取相关的知识点。然而,我可以根据HOLLiAS-LEC G3 PLC这个主题,根据一般...

    Week1—4_Note_Lec1—6.pdf

    Week1—4_Note_Lec1—6.pdf

    lec.zip_LEC _点名_点名系统_点名系统C_随机点名

    总结而言,“lec.zip_LEC _点名_点名系统_点名系统C_随机点名”提供的是一款基于C语言实现的随机点名系统,它集成了随机数生成和数据管理技术,为教学环境带来了便捷和公平。通过理解和掌握这样的系统,不仅可以提升...

    立华 LEC-3210 19’@2U嵌入式通讯管理机 产品介绍.pdf

    立华LEC-3210是一款19英寸标准2U机架式的嵌入式通讯管理机,针对电力自动化行业设计,具备无风扇和低功耗的特点。该设备基于Intel Atom D525双核处理器,搭载6个千兆以太网接口(GbE)、10/18个串行通信接口(COM)...

    Lec-1-SDLC.rar_LEC

    "Lec 1 SDLC.pptx"这个文件很可能包含了对SDLC和UML的详细介绍,涵盖了以上各个阶段的理论知识和实际应用案例。通过学习这个讲座材料,可以深入理解这两个概念如何在实际项目中协同工作,提升软件开发的效率和质量。...

    demo_Lec.m

    demo_Lec.m

    Lec1-Introduction.pdf.zip.zip

    Lec1-Introduction.pdf.zip

Global site tag (gtag.js) - Google Analytics