1. Trang chủ
  2. » Công Nghệ Thông Tin

06 bai giang xu ly song song

417 1,3K 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 417
Dung lượng 5,65 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Ví dụ như mỗilần “cache” đọc/ghi dữ liệu, nó đọc/ghi cả một vùng liên tiếp của RAM, mà dữ liệu tínhtoán thì thường là các mảng và được lưu dưới dạng các byte liên tiếp!.Quá trình CPU gia

Trang 1

Bài Giảng

Xử Lý Song Song

Phạm Thế Long

Trang 2

Hà Nội 2005

Trang 3

Tài liệu này được viết ra là để tỏ lòng biết ơn tới

tất cả các thầy giáo và thủ trưởng đại đội 186.

Trang 4

Lời nói đầu

Tài liệu được biên soạn dựa trên bài giảng cho sinh viên trường đại học Dân-Lập

Đông-Đô, học viện Kỹ Thuật Quân-Sự, trường đại học Sư Phạm Hà Nội

Tác giả xin chân thành cảm ơn sự giúp đỡ rất cảm động của tập thể học viên lớp cao họcK16 Học Viện Kỹ Thuật Quân Sự, K15 trường đại học Sư Phạm Có nhiều trình ví dụtrong bài giảng này là do học viên của lớp viết ra Tác giả xin bày tỏ sự cảm ơn tới anhTrần Bộ, học viên K16 HVKTQS, đã kiểm tra và tu chỉnh lại nhiều chương trình trongbài giảng này

Chẳng thể tránh được sai sót, tác giả rất biết ơn mọi sự chỉ giáo của độc giả

Mục tiêu Bài giảng nhằm vào việc giúp cho độc giả hiểu được những nét chính sau:

- Nhận thấy sự khác biệt giữa thuật toán song song với tuần tự Các bước cần thiết

để xây dựng một thuật toán song song giải một bài toán kỹ thuật

- Khả năng lập trình và những điều có thể xảy ra khi chạy trình song song

- Cung cấp một số trình minh họa giúp cho độc giả hiểu được đúng hơn cách dùngcác lệnh và nên dùng chúng trong những tình huống nào

Đối tượng môn học Triển khai công việc lập trình trên một mạng máy tính, trong đó

mỗi máy tính có thể có nhiều vi-xử-lý hoạt động song song

Yêu cầu Người học cần phải biết ngôn ngữ lập trình C, Java, và cấu trúc lôgích của một

máy tính nói riêng cũng như mạng máy tính nói chung

Trang 5

Mục Lục

Trang 6

I.2 Cấu trúc máy tính 50

Trang 7

II.5.4-D Một số sơ đồ Runge-Kutta thường dùng 95

Trang 8

III.5.3 Hiệu ứng chậm khi dùng lệnh non-blocking 152

Trang 9

V.5.2-A Trường hợp Barrier-Bcast 190

VI.3.1 Tính tích phân 

b a

dx ) x

Trang 10

VI.3.3 Phương trình dao động

x

u t

u

2

2 2 2

2 2 2

2

y

u x

u t

u

u t

u

2

2 2

VII Nguyên lý các trình song song dùng chung bộ nhớ 254

Trang 11

VII.2.3 Sự khác nhau giữa thread và trình con 267

VII.3 Hiệu ứng rượt đuổi & khái niệm vùng lệnh tuần tự 271

Trang 12

VIII.2.5 Thuật toán Edsger Dijkstra's 300

VIII.4 Biến điều kiện & Qui trình đồng bộ hóa thread 311

VIII.6.1 Bài toán sản-xuất & tiêu-thụ (Producer & Consumer ) 332

Trang 13

IX OpenMP 343

IX.7.2 Giải phương trình

x

u t

u

2

2 2 2

Trang 14

XI.3 Vùng CS (Critical Secsion) 399

Trang 15

ra

Ngôn ngữ C++ do Bjarne Stroustrup (phòng thí nghiệm Bell Lab) tạo ra vào đầu nhữngnăm 80 Nó được tạo ra để kết hợp các đặc tính tốt của C (như khả năng can thiệp đượcvào các thiết bị phần cứng của máy tính) với Simula-67 (khả năng hướng đối tượng) vàAlgol-68 (khả năng chạy trình từng phần)

C và C++ được tạo ra để phục vụ cho các mục đích khác nhau Ngôn ngữ C thích hợpcho kiểu tư duy lập trình “dưới-lên-trên” Đó là kiểu lập trình: tạo ra các trình con trướcrồi sử dụng nó để tạo ra các chương trình phức tạp hơn Ngôn ngữ lập trình C++ thìthích hợp cho kiểu tư duy lập trình “trên-xuống-dưới” Quá trình lập trình bắt đầu từviệc phân tích bài toán thành các nhiệm vụ và sử dụng chúng để giải quyết bài toán đặt

ra Các nhiệm vụ lại được phân rã tiếp thành các nhiệm vụ nhỏ hơn

Sau đây là tóm lược (chỉ để phục vụ cho bài giảng) về C & C++ Việc làm này là không

dễ, bởi các khái niệm cơ bản thường đan xen vào với nhau khó có thể phân định rạch ròicái nào cần phải được định nghĩa trước và cái nào sau; việc làm này là cũng không dễ,bởi mỗi trình dịch lại sử dụng các thư viện khác nhau

Để cho đơn giản, các trình ví dụ có trong bài giảng này được viết bằng Visual C 6.0 củaMicrosoft (có sử dụng thêm một số thư viện cần thiết để dịch và chạy trình)

Để thuận lợi cho công việc, đề nghị độc giả dịch và chạy chương trình đơn giản nhất,trình “Hello World” sau đây

Trang 16

Ý nghĩa của việc chạy thành công trình “Hello World” không chỉ ở chỗ nó cho thấy hệthống trình dịch C++ (của độc giả) đã được cài đặt đúng, mà quan trọng hơn, nó – bằngcách chèn vào chỗ cần thiết trong chương trình dòng lệnh cout << “Hello World”; – chỉcho chúng ta một cách để biết được trình đang chạy qua lệnh nào!?

I.1.2.A Cache và quá trình đọc-ghi dữ liệu.

Máy tính được tạo ra từ 2 phần cơ bản:

CPU – bộ xử lý trung tâm: nó có khả năng truy cập vào tất cả các “ngóch ngách” củamáy tính để gửi và nhận dữ liệu, nó có một bộ phận hỗ trợ thực hiện các phép toán đơngiản như + - * / b)

RAM – bộ nhớ: nơi đây người ta lưu qui trình tính toán cùng với dữ liệu cần cho nó

Bộ nhớ, RAM, gồm các byte nhớ – còn được gọi là ô nhớ2 – kết hợp lại với nhau Đểphân biệt, chúng được đánh số liên tiếp tăng dần từ 0

Muốn đọc hoặc ghi dữ liệu ra các ô nhớ này, thì địa chỉ của chúng phải được tải vào cácthanh ghi; nếu thanh ghi 16 bit thì ta có thể qui chiếu tới 2 16 ô nhớ khác nhau, và nếu nó

là 32 bit thì 2 32 ô nhớ khác nhau3 Địa chỉ của một ô nhớ gồm 2 phần, phần thứ nhất gọi

là “segment” và phần thứ hai gọi là “offset” “segment” lưu địa chỉ đầu tiên của vùngnhớ cần qui chiếu đến, và “offset” là địa chỉ tương đối kể từ “segment” Tùy theo cấutrúc của hệ thống qui chiếu địa chỉ, mà địa chỉ vật lý của ô nhớ được qui về “segment”

và “offset” Nếu địa chỉ vật lý có dạng 2seg  offset và các thanh ghi seg , offset đều

2 n bit thì dung lượng bộ nhớ mà CPU có thể quản lý được là 2 n ( 2 1 )

Quá trình ghi và đọc dữ liệu không hề đơn giản, và thời gian tiêu tốn do tín hiệu xungđiện mang các dữ liệu ấy phải chạy một đoạn đường xa từ CPU ra đến RAM là chủ yếu– cho dù dòng điện chạy với tốc độ ánh sáng, là tốc độ nhanh nhất có thể Để vi xử lýkhông phải chờ dữ liệu đi lại, người ta thiết kế ra một vùng nhớ phụ ở ngay trong CPU,

và gọi nó là “cache” “Cache” lưu giữ cả địa chỉ ô nhớ và dữ liệu có trong ô nhớ ấy Mộtkhi CPU cần dữ liệu thì “cache” sẽ đọc/ghi chúng từ RAM vào để cung cấp cho CPU.Như vậy, CPU chỉ cần tiếp xúc với “cache” mà không cần phải truy cập vào RAM Quátrình “cache” đọc dữ liệu có thể xảy ra trước khi CPU cần đến chúng, do “cache” có thểphát hiện được các lệnh truy cập bộ nhớ sớm hơn CPU, và quá trình cache ghi dữ liệu raRAM có thể là sau, cho dù CPU đã thực hiện xong lệnh ghi dữ liệu, và đang đi thực hiệncác lệnh tiếp theo Với cơ chế này hiệu quả tính toán chung tăng lên rất nhiều Hiệu quả

2 mỗi ô có thể nhớ được 256 trạng thái, đánh số từ 0 255.

3 một phần của chúng được để ở đĩa cứng Khi cần các dữ liệu ở đĩa cứng này, chúng sẽ được chuyển vào RAM

Trang 17

tính toán còn được tăng lên do việc các ô nhớ thường được dùng đi dùng lại, và như vậythì giao tiếp của CPU với bộ nhớ trên thực tế chỉ là giao tiếp với “cache” Ngoài ra chiếnlược “caching”, tức là chiến lược lưu giữ tạm thời dữ liệu cũng được người ta nghiêncứu để tăng khả năng mà “cache” có thể cung cấp được dữ liệu cho CPU Ví dụ như mỗilần “cache” đọc/ghi dữ liệu, nó đọc/ghi cả một vùng liên tiếp của RAM, mà dữ liệu tínhtoán thì thường là các mảng và được lưu dưới dạng các byte liên tiếp!.

Quá trình CPU giao tiếp với “cache” và “cache” giao tiếp với RAM là các quá trình vậnhành tương đối độc lập Điều này, một mặt làm cho hiệu quả tính toán tăng lên, một mặtkhác có thể gây ra sự phiền toái Sự phiền toái xảy ra do RAM là vùng nhớ dùng chung

và có thể có nhiều thiết bị cùng một lúc truy cập vào Vậy một khi CPU ghi dữ liệu raRAM mà dữ liệu lại không ra ngay, thì các thiết bị khác đọc được dữ liệu nhưng lại là dữliệu cũ không phải là thứ cần thiết Để cấm không cho các thiết bị khác truy cập vào một

ô nhớ khi lệnh đọc/ghi dữ liệu của CPU đang được thực hiện, thì “cache” phải chuyểntải dữ liệu ngay ra RAM và máy tính phải ở vào trạng thái đặc biệt và trạng thái nàyđược gọi là “lock”, khóa ô nhớ

Đối với máy tính có nhiều vi xử lý cùng dùng chung một vùng nhớ RAM, lập trình viênphải lưu tâm đến, không chỉ cơ chế giao tiếp gián tiếp qua “cache” của CPU và RAM,

mà cả nguy cơ các vi xử lý cùng lúc truy cập vào một ô nhớ khi nó đang ghi dữ liệu

I.1.2.B Process & Thread

Vào thời kỳ đầu những năm 70, bộ lệnh (assembly) cho máy tính rất đơn giản Nó thậmchí chẳng có cả lệnh “nhảy” jump, hay lệnh gọi trình con Vi-xử-lý chỉ làm theo các lệnhđược hiện ra ở thanh ghi mã lệnh IP Mà các con số hiện ra trên IP thì cứ tuần tự tăngdần, mỗi lần 1 đơn vị Muốn lệnh tiếp theo phải thực hiện là một lệnh nào đó ở vị tríkhác, không phải nằm ngay sau lệnh vừa thực hiện, thì lập trình viên phải đánh lừa vi xử

lý Cụ thể là họ phải xác định được vị trí của lệnh này, sau đấy “nhét” nó vào thanh IP và

“chờ” cho vi-xử-lý bị mắc lỡm4 Lệnh gọi một trình con thì phức tạp hơn một tí, bởi vìsau khi kết thúc hoạt động của trình con thì lệnh tiếp theo phải là lệnh tiếp theo lệnh đãgọi nó

Ngày nay bộ lệnh (assembly) cho máy tính đã thay đổi nhiều Quá trình tích lũy kinhnghiệm tin học trong 50 năm đã đúc kết lại và hình thành ra các nguyên lý về cấu trúcmáy tính và lập trình, các nguyên lý này được “thể hiện” ra ở các lệnh assembly Các thủtục như chuyển điều khiển và gọi trình con đã và chỉ còn là các lệnh đơn giản Cấu trúccủa một chương trình cũng có thay đổi Dữ liệu được phân ra làm nhiều loại như:

1 Dữ liệu tĩnh (gọi đơn giản là dữ liệu) là vùng nhớ cố định nơi lưu giữ các biến(global) dùng cho toàn bộ chương trình và các trình con

4 Những kiến thức này do các thày giáo Nguyễn Xuân My, Nguyễn Vy (dạy đại đội 186) dạy

Trang 18

2 Stack là vùng nhớ có thể dùng đi dùng lại nhiều lần

3 Heap là vùng nhớ chỉ gồm các byte không có định dạng

Mã lệnh của chương trình được lưu vào một vùng, gồm các byte liên tiếp, được gọi làsegment mã lệnh (cseg segment) Dữ liệu được để vào một segment (dseg segment) nằmliền sát với segment mã lệnh Các biến (tức vùng nhớ có độ dài nhất định nhưng nộidung thì có thể thay đổi) được lưu vào vùng nhớ có tên là stack Đó là một vùng nhớ mà

cứ mỗi khi có một biến được khai báo thì stack lại tăng thêm một “ngăn” cho nó

int 21h mov ax, 4C00h int 21h

cseg ends dseg segment byte

Như vậy, mỗi một chương trình được phân ra làm các phần chính như vùng mã lệnh,vùng dữ liệu, vùng stack, vùng heap, và còn một vùng nữa để giao tiếp với hệ điều hành

Trang 19

(ví dụ như để đọc và ghi dữ liệu ra file) Các yếu tố này lập thành một thể thống nhất vàđược gọi là tiến trình (process)

Chương trình, kể cả khi nó đã được dịch ra lệnh máy, cũng chỉ là phần mã lệnh (được đểvào vùng mã lệnh), trong khi tiến trình, tức process, thì bao gồm mã lệnh và các yếu tốphục vụ cho nó hoạt động (như nói ở phần trước)

Một trình con chỉ là một đoạn mã lệnh của trình chính (nhưng được dùng nhiều lần), vìvậy mã lệnh của nó cũng được để ngay ở vùng segment mã lệnh Trước khi gọi, các giátrị cần truyền cho trình con được nhét vào stack – trình con sẽ vào stack để lấy chúng ra.Mỗi trình con có thể có biến riêng, vì thế chúng cũng có stack và stack của nó là stackcủa trình chính – trình con chỉ dùng nhờ (vì đây là một quá trình tuần tự, khi trình conchạy thì trình chính ngừng hoạt động) Phần stack mà trình con dùng “nhờ” sẽ bị mất đikhi trình con kết thúc hoạt động

Việc phân bổ chi tiết các vùng nhớ được trình dịch thực hiện Lập trình viên chỉ việckhai báo các segment với các nội dung cần thiết

Đối với trường hợp các trình con có thể được kích hoạt để hoạt động song song với nhau(và song song với cả trình chính), thì trình dịch phải bố trí cho chúng vùng stack hoàntoàn riêng biệt (không thể sử dụng “nhờ” được nữa!)

Các trình con loại này được gọi là thread Giống với trình con, các thread có thể truy cập(để đọc và ghi) các biến dữ liệu chung ở vùng (data segment); và khác với các trình con

là chúng có thể tranh nhau ghi và đọc vào các ô nhớ chung này

Bảng liệt kê sự khác nhau giữa thread và process

Trang 20

thread process

thread không có data segment & heap có code segment, data segment, stack

segment, heap & một số phần I/O nữa

có thể có nhiều thread trong một

process, và main() là một trong số đó

thread trong cùng một process dùngchung nhau code/data/heap và I/O; nhưngchúng có stack segment, registers riêng.tạo ra một thread mất ít thời gian tạo ra một process mất nhiều thời gian

khi thread kết thúc hoạt động vùng bộ

nhớ stack của nó được giải phóng

khi process kết thúc hoạt động, tất cả cácthread kết thúc theo

Hình vẽ sau mô tả vị trí stack của các thread trong cùng một process Các stack đều nằmtrong vùng stack của process (trình chính), nhưng nằm ở những vị trí khác nhau Vì vậymỗi thread phải nhớ lấy địa chỉ vùng stack của mình

Trang 21

I.1.3 Trình dịch “Complier” & trình liên kết “Link”

I.1.3.A Hai cách đặt tên.

Có thể dùng một số byte liên tiếp – “vùng nhớ” – để nhớ một lượng thông in nào đó; và

để tìm lại được nó với mục đích đọc/sửa thông tin ấy (truy cập hay qui chiếu), chúng ta

cần phải biết là ta đã dùng bao nhiêu byte vào việc này – dung lượng, và địa chỉ vùng

nhớ nơi đã lưu chúng (địa chỉ vật lý của byte đầu tiên)

Việc làm phức tạp này có thể được thực hiện đơn giản bằng một trong 2 cách sau:

Đặt cho “vùng nhớ” ấy một cái tên – tên này vừa phải cho biết địa chỉ vật lý, vừa phải cho biết dung lượng, và vừa phải cho biết dạng của “vùng nhớ”

Chứa địa chỉ của byte đầu tiên của “vùng nhớ” vào một nơi và đặt tên cho chỗ ấy – con

trỏ (pointer – luôn chiếm 4 byte) Mỗi khi cần truy cập đến vùng nhớ ta sử dụng tên contrỏ để tìm thấy địa chỉ của “vùng nhớ” Con trỏ vừa phải cho biết địa chỉ vật lý của vùng

nhớ, vừa phải cho biết độ lớn của nó, và vừa phải cho biết nó thuộc dạng nào.

Cách thứ nhất, về bản chất, đi kèm với mỗi tên là một con trỏ (ký hiệu là &tên) – trỏ

vào địa chỉ byte đầu tiên của vùng nhớ Cách thứ hai, những gì chứa trong pointer có thểthay đổi Chính vì vậy, bằng cách thay đổi giá trị con trỏ ta có thể quy chiếu nó đến mộtvùng nhớ khác

Trình dịch “Compiler” và trình liên kết “Link” quản lý việc tương ứng các tên tới các

vùng nhớ cụ thể Việc của chúng ta là khai báo5

Định nghĩa

Mảng là tên một số “vùng nhớ” liên tiếp được đánh theo chỉ số Ví dụ như a 0 , a 1 , , a 19

là mảng a, hay b 0 , 0 , b 0 , 1 , , b 19 , 49 là mảng b Các “vùng nhớ” trong cùng mộtmảng đều phải thuộc cùng một định dạng

Ví dụ khai báo “int a[20];” hàm ý: với mỗi chỉ số k trong khoảng từ “0 19” thì “a[k]” làmột số nguyên Cũng vậy, khai báo “float b[20][50];” hàm ý: với mỗi chỉ số 0k19 vàvới mỗi chỉ số 0h49 thì “b[k,h]” là một số thực

Trang 22

khối lệnh được dùng nhiều lần thì người ta đặt tên cho nó và gọi nó là hàm

Tên hàm khác với tên biến ở chỗ tên hàm đi kèm với ngoặc ( ) Như vậy, A thì là biến,

mà Ặ ) thì là hàm Mỗi khi gặp tên hàm này, CPU lần ngược trở lại để tìm khúc lệnhtương ứng và thực hiện Mỗi hàm được coi là một lệnh

Một hàm và một khối lệnh còn khác nhau ở chỗ, hàm được mô tả một cách hình thức từtrước với các biến hình thức Các biến hình thức là các ô nhớ có tên nhưng chưa có giátrị, giá trị của nó sẽ tùy thuộc vào nhu cầu cụ thể và được gán saụ

Các biến hình thức được khai báo trong ( ) của hàm khi mô tả hàm, và nó sẽ được gán

giá trị trực tiếp, hay nhận giá trị từ một biến nào đó

sử dụng hai biến hình thức X, n để tính Y=Xn

Khi lập trình (viết ra các dòng lệnh trên) các biến X và n đều chưa có giá trị Chúngđược dùng trong khối lệnh trên chỉ thể hiện thuật toán tính ra Y=Xn mà thôị

Để biết được giá trị 210 chúng ta đưa ra lời gọi power (2,10);

Trang 23

giá trị fib(n) = fib(n - 1) + fib(n- 2) với fib(0) = 0, và fib(1) = 1;

I.1.3.C Truyền tham số cho hàm

Có hai cách truyền thông số cho hàm: cách thứ nhất là truyền trực tiếp giá trị, và cách thứ hai là truyền địa chỉ nơi lưu giữ giá trị cần truyền Chúng ta cùng theo dõi hai trình

sau đây để thấy sự khác nhau của chúng

Trình này thực hiện sai ý định Trình này thực hiện đúng ý định

Trang 24

x=7 y=5

x=7 y=5x=5 y=7

Các hàm sw() được viết ra với mục đích thực hiện việc trao đổi giá trị của 2 biến x, ycho nhau Tuy nhiên kết quả thực hiện của chúng lại là khác nhau Nguyên nhân của sựkhác nhau này không phải là do thuật toán sai, mà là ở cách chúng ta truyền tham số chochúng

Trong trình thứ nhất, với khai báo

void sw(int x, int y)

thì hai biến mới (với tên gọi là x và y) kiểu số nguyên sẽ được hàm tạo ra, chúng được

nạp giá trị (truyền trực tiếp giá trị)

Trong trình thứ hai, với khai báo

void sw(int *x, int* y)

thì chúng ta đã truyền 6 cho hàm địa chỉ (&x là địa chỉ của x, &y là địa chỉ của y) của các

ô nhớ x và y; và chính vì vậy, các biến đổi sẽ trực tiếp ảnh hưởng tới giá trị của các biến

x và y

6 Thực tế là hai ô nhớ địa chỉ, với tên gọi là x và y, đã được tạo ra và các địa chỉ được truyền vào các ô nhớ vừa được tạo ra này.

Trang 25

Trong C người ta thường sử dụng cách truyền địa chỉ

Hai trình con sau đây là tương đương với nhau; chức năng của chúng là tính tổng hai số

cout << sw(&x,&y) <<"\n";

}

Trang 26

1 Dạng (Type) của một vùng nhớ, được tạo ra từ một số byte liên tiếp, là cách thức

mà “những gì” được chứa ở trong đó sẽ được máy tính xử lý Bản thân vùng nhớ

ấy được gọi là Biến (Variable) Những gì chứa trong biến được gọi là dữ liệu.

2 Nếu một vùng nhớ, ngoài phần chứa dữ liệu đã được định dạng, còn có các byte chứa mã lệnh qui định cho máy tính phải làm gì với các dữ liệu ấy thì nó được gọi là “được định dạng theo kiểu Lớp (Class)” Bản thân vùng nhớ ấy được gọi

là Đối tượng (Object).

Lưu ý

- Đối với lớp (class), các lệnh ở trong phần byte chứa mã lệnh là hoàn toàn xácđịnh Chính vì vậy, phần chứa mã lệnh của các đối tượng (object) cùng một lớp(class) là giống nhau hoàn toàn Do giống nhau, ta chỉ cần lưu giữ một bản! – vìvậy trình dịch (compiler) để chúng ra một vùng khác Bản thân “đối tượng” sẽ chỉlưu giữ các byte chứa dữ liệu (là các byte nội dung có thể thay đổi)

- Nhìn vào bộ nhớ8 thì chúng ta không thể phát hiện ra một dãy các ô nhớ thuộc

dạng 9 nào Ta chỉ có thể biết được dạng của chúng thông qua việc xem chương trình trước khi nó được dịch Trong chương trình, người ta tạo ra các dạng mới bằng cách đặt cho nó một cái tên và khai báo nó được tạo ra từ các dạng đã có

(thứ tự trong khai báo “trước-sau” tương ứng với vùng nhớ có địa chỉ “cao-thấp”)

Để cho tiện, các dạng mới được tạo ra bằng cách liệt kê này, được gọi là cấu trúc

- Nếu vidu là tên của dạng (hay lớp) thì với khai báo:

7 Những gì được lưu ở trong byte có thể là dữ liệu (máy tính sẽ xử lý chúng) mà cũng có thể là mã lệnh (buộc máy tính phải làm gì).

8 Nội dung chứa ở bên trong các ô nhớ chỉ là các “hình vẽ ” !?

9 Đối với ngôn ngữ giao tiếp thông thường, lớp (class) cũng là dạng Vì vậy, trong một số trương hợp nếu

không gây ra nhầm lẫn, chúng ta dùng chữ dạng làm đại diện cho cả hai, dạng và lớp Tuy nhiên lớp thì có thể

chứa các dạng, nhưng ngược lại thì không Dạng được tạo ra từ các dạng cơ bản, còn lớp thì được tạo ra từ các dạng và các hàm.

Trang 27

o vídu X[n]; n là một hằng số

trình dịch sẽ tạo ra các biến X[0], X[1], X[n-1] nếu “vidu” là dạng, và cácđối tượng nếu “vidu” là lớp Nói một cách chính xác hơn, trình dịch sẽ dành

ra một vùng nhớ cho X[0], X[1], X[n-1] và sẽ xử lý các dữ liệu10 có ở trong

đó theo cách mà chúng phải bị biến đổi

Sử dụng ký hiệu X[k].what để tham chiếu đến thành phần “what” của X[k]

o vídu *Y = new vídu[n]; n là một hằng số

trình dịch sẽ tạo ra một con trỏ Y (chỉ chiếm 4 byte) chứa địa chỉ là byte đầucủa một vùng nhớ Y[0], Y[1], Y[n-1] được định dạng là “víduY là biếnđộng nếu “vídu” là con trỏ dạng, và đối tượng động nếu “vídu” là con trỏlớp

Sử dụng ký hiệu Y[k]->what để tham chiếu đến thành phần “what” của &Y[k].Khi không còn cần thiết, ta có thể hủy bỏ 11Y để giải phóng bộ nhớ

Để hủy bỏ vùng nhớ đã cấp cho một con trỏ ta dùng lệnh

int age;

} student;

void main() { student Hien = {"Pham Minh Hien", 23};

cout << Hien.name << " tuoi " << Hien.age << "\n";

Hien.age=24;

cout << Hien.name << " tuoi " << Hien.age << "\n";

}dạng student từ các dạng cơ bản “char, int” Dạng này tác động lên 65 byte (“Hien”) nằmliền nhau trong bộ nhớ12 Trong 65 byte đó thì 63 byte dạng “char” với tên gọi “name”, 2

10 Những gì có sẵn trong các byte, khi một biến được tạo ra, khiến cho biến có ngay một giá trị nào đó mà chúng

ta gọi là “rác”.

11 Mặc dù đối tượng đã bị xóa nhưng con trỏ vẫn còn.

12 Số lượng byte thực tế cấp cho mỗi một vùng nhớ phụ thuộc vào nhiều yếu tố Nếu máy tính vận chuyển một lúc 4 byte (32 bit), và trình dịch “tối ưu”, thì số lượng byte nó cấp mỗi vùng nhớ trong một cấu trúc struct (hay

Trang 28

byte dạng “int” với tên gọi “age”.

Sơ đồ sau cho chúng ta một mường tượng về thứ tự địa chỉ của các ô nhớ nói trên

là Destructor Constructor sẽ được kích hoạt để chạy mỗi khi có một Object được tạo ra

Và Destructor sẽ được kích hoạt để chạy mỗi khi chúng ta hủy bỏ Object

Có thể truy cập đến các biến và hàm nằm trong phần public: nhưng chỉ các hàm củachính đối tượng mới truy cấp được vào các biến và hàm nằm trong phân private:

Trong ví dụ sau Dog là tên của một Class Nó tác động lên vùng nhớ “weight” (dùng đểlưu 1 số nguyên) và “vùng nhớ13” để lưu giữ tên của một số hàm Trong trình có 1 đốitượng tĩnh và 1 đối tượng động được tạo ra, và như vậy 2 lần hàm Constructor vàDestructor được gọi đến (theo dõi dòng chữ in ra trên màn hình)

void setWeight(int weight);

một lớp class) được làm tròn thành bội của 4 Như vậy student sẽ chiếm một vùng nhớ 68 byte Trong đó 64 byte cho name, và 4 byte cho age Thực tế nó chỉ dùng 65 byte, các byte còn lại là thừa Lệnh cout <<

sizeof(student); in ra 68, là số lượng byte mà student được cấp.

13 Cho dù là khai báo lớp (class) Dog rất “đồ sộ”, nhưng đối tượng do nó tạo ra chiếm một vùng nhớ chỉ vừa đủ

để chứa một số nguyên “weight”!

Trang 29

int getWeight();

};

Dog::Dog() { weight = 0; cout << "Dog Constructor Called \n";}

Dog::~Dog() { cout << "Dog Destructor Called \n"; }

void Dog::setWeight(int weight) { this->weight = weight; }

int Dog::getWeight() { return weight; }

void main()

{ Dog rover; Dog *Becgie = new Dog;

rover.setWeight(10); //Updating Rover's Weight;

cout << "now Rover weighs " << rover.getWeight() <<"\n";

void setName(string name) {_name = name;}

string getName() {return _name;}

void setColor(string color) {_color = color;}

string getColor() {return _color;}

void speak() {cout << "meow" << endl;}

private:

string _name;

Trang 30

string _color;

};

int main()

{

Cat cat1("morris","orange"); //Objects of automatic extent exist on stack.

Cat *cat2pt = new Cat("felix","black"); /* Dynamically allocated objects exist on the heap */ Cat *cat3pt = new Cat; //Calls default constructor

cout << cat1.getName() << " is " << cat1.getColor() << endl;

cout << cat2pt->getName() << " is " << cat2pt->getColor() << endl;

cout << cat3pt->getName() << " is " << cat3pt->getColor() << endl;

Có thể định nghĩa mảng các đối tượng cùng một lớp

Với định nghĩa lớp “Cat” như trên, câu lệnh

Cat *catpt = new Cat[27];

tạo ra con trỏ tới mảng gồm 27 “con mèo”

Câu lệnh

delete[] catpt;

xóa vùng nhớ đã cấp cho 27 “con mèo”

I.1.4.B Các dạng dữ liệu cơ bản.

2000 và 5, là những gì mà chúng ta đang nhìn thấy chúng có thể là hai số nguyên –

dạng “int” và cộng với nhau thì được 2005 chúng cũng có thể chỉ là hai dòng chữ số –

dạng “string” và “cộng” lại với nhau thì được 20005 Như vậy, ta chưa nên khẳng định

Trang 31

ngay 2000 là một số (!), vấn đề quan trọng lại là ở chỗ “nó thuộc dạng nào”!!

Trình14 ví dụ sau minh họa điều trên

cout << a+b;

}

Nguyên nhân xuất hiện các dạng khác nhau là nằm ở tính triết lý của quá trình nhận biết

Lấy ví dụ như việc xuất hiện dạng “int” có bản chất sâu xa là ở chỗ “các số thực là các

số thập phân vô hạn!” Sự vô hạn này không gây ra phiền toán cho tới khi nào chưa cầnphải ghi nhớ lại giá trị của chúng Mọi sự ghi nhớ đều là hữu hạn, vậy để ghi nhớ một sốthực thì, chúng ta phải chấp nhấp nhận ghi một số lượng hữu hạn số thập phân của nó –

dạng “float” (ghi lại 18 chữ số sau dấu phảy) Sự việc có thể tai hại đến mức, khi chúng

ta ghi giá trị là 1 vào một biến “float” nào đó thì, rất có thể, khi đọc lại sẽ được số

000000001 0000000000

,

các số dạng “float” – lẽ dĩ nhiên sẽ không thể chính xác hoàn toàn – bởi (chưa cần phải thực hiện các phép toán) bản thân các biến “float” ấy cũng chẳng thể “giữ được” giá trị

của mình một cách “tuyệt đối chính xác!”

Khúc trình sau khai báo “foat x;” và chứa 1 000001 vào đấy Rõ ràng là

000001 1

“quan niệm” khác đi (tức là lưu giữ ở dạng khác), chúng được mô tả ở dạng “int”

14 Các trình ví dụ trong bài giản này thường được viết cho ngắn lại; vì thế, nếu các lệnh cùng phục vụ cho một mục đích sẽ được gom lại trên một dòng.

Trang 32

I.1.4.C dạng “extern”

Thứ tự bắt buộc là trình con phải xuất hiện trước (nằm ở trên) khi nó được gọi đến (cộtkhai báo thuận) Nếu muốn để trình con ở sau, thì sự tồn tại của nó vẫn phải được khaibáo ở trên (cột “khai báo ngược”) Để dễ hình dung chúng ta xét hai trình ví dụ sau(chúng tương đương nhau) Sự khác nhau là về thứ tự của trình “void Write( )” và trìnhchính “void main()”

#include <iostream>

#include <string>

using namespace std;

#define MY_STRING "Hello World"

string AnotherString("Hello Everyone");

void Write (char *ThisString)

#define MY_STRING "Hello World"

void Write (char *ThisString)

string AnotherString("Hello Everyone"); void main()

có những khai báo này, họ sử dụng chỉ thị #include < *.h> để chèn nó vào

Do một chương trình có thể phân ra làm nhiều file, dịch độc lập với nhau, mà xuất hiệnnhu cầu về loại biến “external” Khi biên dịch, các file sẽ được chuyển thành các filechứa mã lệnh assemply, và chính vì vậy, “địa chỉ” của các biến cũng hoàn toàn xác định.Nếu chúng ta muốn một biến nào đó ở các file khác nhau nhưng về bản chất chỉ là một,thì chúng ta phải “để” các biến này vào cùng một địa chỉ15 vật-lý Việc làm này được

15 Trình “link” sẽ làm nhiệm vụ chuyển tất cả các biến cùng tên có cùng khai báo “external” ở trong tất cả các file

Trang 33

triển khai theo qui trình sau:

- Các biến ấy cho dù ở các file khác nhau nhưng phải có cùng tên gọi

- Phải có thông báo “extern” khi khai báo biến ấy

#include <iostream>

#include <string>

using namespace std;

#define MY_STRING "Hello World"

void Write (char *ThisString);

file

#include "header.h" // chèn file “header.h”

extern string AnotherString("Hello Everyone");

#include "header.h" // chèn file “header.h”

extern string AnotherString;

void Write (char *ThisString)

“Write.cpp” Thư viện

Theo như cách phân chia trên, trình được phân thành 3 file “header.h”, “Test.CPP”,

“Write.CPP” độc lập với nhau với các tên gọi file “mô tả” (protocol), file “trình chính”(có dòng lệnh “main()”), và file “thư viện” Trình dịch “Compiler” cho phép dịch riêng

rẽ từng file “Test.CPP” và “Write.CPP” (có kiểm tra lỗi) ra mã lệnh assemply Sau đótrình liên kết “Link” sẽ kết hợp chúng lại với nhau (các biến “extern” sẽ được trình

“Link” qui chiếu về cùng một địa chỉ vật-lý)

Thư viện được tạo ra từ các hàm cần cho trình chính Một số hàm thông dụng được dịchsẵn và chúng được để vào các file với tên gọi “*.lib”

I.1.4.D Dạng hằng “constant”

Biến hằng “constant” – giá trị của nó được gán ngay khi nó được tạo ra và không thể

vào cùng một địa chỉ.

Trang 34

thay đổi16 Chúng ta giải thích nguyên nhân dẫn đến sự xuất hiện và mục đích sử dụngcủa loại dữ liệu này như sau.

Thông thường giá trị của một biến được xác định trong khi chạy trình (trình đã đượcdịch ra mã máy) Tuy nhiên một số giá trị phải hoàn toàn xác định ngay khi trình đượcdịch ra mã máy, ví dụ như độ dài của một vùng nhớ Trình dịch dựa vào độ dài này đểqui hoạch bộ nhớ, tức là, biến này được để từ đâu đến đâu, biến kia được để từ đâu đếnđâu ; và một khi đã qui hoạch rồi thì các gianh giới ấy sẽ không thể thay đổi được nữa.Người ta lưu các giá trị ấy vào các biến thuộc loại “constant17” Giá trị của chúng đượcxác định ngay từ trước lúc chạy trình và sẽ không thay đổi trong toàn bộ thời gian chạytrình

Xét ví dụ sau đây

Đặt giả sử chúng ta cần sử dụng các biến với tên gọi A[0], A[1], A[2], , A[9] (mỗi biếnchứa một số thực có dạng “double”) Trình dịch sẽ đặt các biến này ở các địa chỉ vật lýliền nhau với tên gọi là mảng “A” Như vậy mảng là loại biến có chỉ số

Để khai báo nhu cầu sử dụng mảng A[0], A[1], A[2], , A[9] người ta phải thông báo tên

và độ dài (phạm vi thay đổi của chỉ số) của mảng

16 Thật ra nội dung của các biến “constant” này vẫn có thể thay đổi được (vì chúng chỉ là các byte nhớ mà ta có thể ghi vào bất cứ giá trị gì.) Tuy nhiên, trình dịch sẽ ngăn cản mọi nỗ lực làm thay đổi nội dung của chúng.

17 Một số các thông số quan trọng, như số , hay các hằng số vật lý (như hằng số hấp dẫn trong định luật Vật-Hấp-Dẫn) cũng thường được mô tả là các biến hằng.

Vạn-18 nhu cầu riêng tư “private” này xuất hiện là để giải quyết cái khó khăn của việc đặt tên Hãy tưởng tượng người ta chỉ có thể đặt cho các thành viên trong gia đình các tên gọi khác nhau, chứ không thể moi đâu cho ra

đủ 80 triệu tên để mỗi người dân Viet Nam có được một tên gọi khác nhau!

Trang 35

I.1.4.F Dạng “static”

Biến “static” là biến mà giá trị của nó “vẫn còn19” khi trình con kết thúc Chúng ta giảithích nguyên nhân dẫn đến sự xuất hiện và mục đích sử dụng của loại dữ liệu này nhưsau

Đặt giả sử có một tình huống, khi mà một trình con được gọi lại nhiều lần và thậm chíchạy song song Mỗi lần chạy trình con này, nó lại tính ra được kết quả và cộng dồnchúng vào một biến tổng nào đó Nơi lưu giữ tổng này phải được khai báo và được gángiá trị ban đầu là bằng 0

Qui trình trên có thể được thực hiện như sau:

- tổng là biến “global” và lệnh gán ban đầu chỉ do trình main() thực hiện

- tổng là biến “static” ở trong trình con, và lệnh gán ban đầu vẫn do trình con “thựchiện”

Chúng ta sử dụng ví dụ sau để minh họa điều vừa nói ở trên Đây là ví dụ về hai trìnhtương đương Trong mỗi trình có hai biến “auto_var” và “static_var” Trong mỗi trình,mỗi khi trình con được gọi tới, cả hai biến này đều được tăng thêm 1 và giá trị củachúng được in ra màn hình Trình con được gọi đúng 5 lần; và chúng ta có thể thấy 5 lầncác giá trị số hiện ra trên màn hình Tuy nhiên, giá trị của biến “auto_var” thì không thayđổi, còn giá trị của “static_var” thì tăng dần

Sự thật là chương trình tác động lên hai biến “auto_var” và “static_var” hoàn toàn nhưnhau Sự khác nhau chỉ là cách và nơi chúng ta đã khai báo chúng và chính sự khácnhau này dẫn đến việc giá trị của chúng thay đổi khác nhau

Biến auto_var là biến của trình con Giá trị của nó được gán lại bằng 0 mỗi khi trình conđược gọi Còn với biến “static_var” thì có hai trường hợp:

- “static_var” trong trình thứ nhất là biến thuộc dạng “global” Giá trị ban đầu đượcgán bằng 0, và lần gán này là duy nhất Cứ sau mỗi lần trình con được gọi lại thì

nó cộng thêm 1 vào biến này; và khi trình con kết thúc hoạt động thì nội dung của

ô nhớ, tức giá trị của biến, “static_var” vẫn còn

- “staic_var” trong trình thứ hai là biến thuộc dạng “static”, nó được khai báo với

chỉ thị “static” Giá trị ban đầu được gán bằng 0 (nó phải được gán trực tiếp 20

ngay khi khai báo21), và do nó được khai báo là “static” mà lần gán này là duy

nhất (cho dù trình con có được gọi lại bao nhiêu lần chăng nữa!) Cứ sau mỗi lầntrình con được gọi lại thì nó cộng thêm 1 vào biến này; và khi trình con kết thúchoạt động thì nội dung của ô nhớ, tức giá trị của biến, “static_var” vẫn còn

19 đây là biến của trình con, và như vậy chỉ có nó mới truy cập đến được.

20 Về thực chất, trình dịch “compiler” gán trước giá trị ban đầu này.

21 static int static_var = 0;

Trang 36

Sự khác nhau duy nhất giữa hai trường hợp là biến “static_var” được khai báo ở trongtrình con thì chỉ nó mới đọc/ghi được

cout<< " auto_var: " << auto_var <<"\n";

cout<< "static_var: " << static_var <<"\n";

static int static_var = 0;

++auto_var;

++static_var;

cout<< " auto_var: " << auto_var <<"\n"; cout<< "static_var: " << static_var <<"\n"; }

void main() { int i;

for (i=0;i<5;++i) stat();

}

I.1.5.A Nhận biết thời gian & Tạo lập ngẫu nhiên

Trong tính toán, nhiều khi xuất hiện nhu cầu nhận biết thời gian và tạo lập sự ngẫunhiên Nhu cầu trên được giải quyết thông qua các lệnh sau

Trang 37

mỗi lần nó ngừng hoạt động trong khoảng thời gian nhỏ nhưng ngẫu nhiên

clock_t start, finish;

I.1.5.B Nhận dữ liệu từ bàn phím & in ra màn hình.

Nạp dữ liệu từ bàn phím có một điểm bất tiện là “ta không rõ vào thời điểm nào thìchương trình đang chờ để nhận dữ liệu!” Để cải thiện tình hình này chúng ta dùng mẹo

“Hello World” – cài vào chương trình lệnh in ra màn hình một thông báo – để thông báonhu cầu ngay trước lúc chương trình chờ nhập dữ liệu

Ví dụ sau minh họa cách nhập dữ liệu vào các biến dạng “string, int, float”

cout << "Cho biet ho & ten: ";

cin >> firstname >> lastname;

Trang 38

I.1.5.C Đọc và ghi dữ liệu ra file.

Ví dụ sau ghi hai số 1234 và 123.4 vào file “C:\Leanh.txt”

#include <iostream.h>

#include <fstream.h>

int main() { ofstream File("c:/Leanh.txt");

int x=1234; float y=123.4;

for (i=1; i<=100; i++) {

x=rand(); y= (float) (rand()*rand())/RAND_MAX;

22 dữ liệu được ghi vào ở dạng nào, thì phải lấy ra ở dạng ấy.

Trang 39

ofstream File(fileName.c_str(), ios::app);

if (! File) { cout << "Error opening output file" << endl; return -1; }

File << "Hello World" << endl;

return 0;

}

Lưu ý rằng nếu chúng ta bỏ chỉ thị “ios::app” ra khỏi dòng lệnh

“ofstream File(fileName.c_str(), ios::app);”

Trang 40

thì tất cả nội dung cũ của file sẽ bị xóa hết Ví dụ sau biểu diễn việc ghi dòng chữ "HelloWorld" vào một file (nội dung cũ của file bị xóa).

ofstream File(fileName.c_str(), ios::app);

if (! File) { cout << "Error opening output file" << endl; return -1; }

File << "Hello World" << endl;

return 0;

}

Dữ liệu được ghi vào file theo cách trên được gọi là ghi ở dạng “text” – cho phép chúng

ta trực tiếp đọc được nội dung file Trên thực tế mỗi khi chúng ta nhìn thấy một số, ví dụnhư 1234, thì chúng ta – do cách thức quan niệm các con số – không hề phân biệt nó làthuộc dạng nào: số thực “float” hay là số nguyên “int” Như chúng ta đã biết, đối vớimáy tính dạng “float” và “int” là khác nhau Vậy nhu cầu chuyển đổi dạng là cần thiết

I.1.5.D Kỹ thuật thao tác với con trỏ.

Pointer, hay con trỏ, là công cụ dùng để định dạng các byte địa chỉ23 Mỗi con trỏ đượccấu tạo ra từ 2 phần: phần thứ nhất là định dạng, phần thứ hai là nơi chứa địa chỉ

Nơi chứa địa chỉ là 4-byte, gồm 32 bit Nội dung các byte này có thể thay đổi và ý nghĩa

của nó là địa chỉ của một vùng nhớ nào đó mà chúng ta quan tâm

Một khi con trỏ được tạo ra thì định dạng của nó không thay đổi, định dạng này qui định

dạng của các byte để ở địa chỉ mà nó giữ ở “nơi chứa địa chỉ”

Toán tử & cho biết địa chỉ của một biến

#include <iostream.h>

void main() { int c, d;

cout << &c << " " <<&d << "\n";

}

23 #include <iostream.h>

void main() { int c; cout << &c ;} // “&c” là địa chỉ của biến “c”.

Ngày đăng: 22/09/2018, 16:39

Nguồn tham khảo

Tài liệu tham khảo Loại Chi tiết
1) S. N. Papakostas, G. Papageorgiou, “A family of fifth-order Runge-Kutta pairs” – MATHEMATICS OF COMPUTATION Volume 65, Number 215. July 1996, Pages 1165-1181 Sách, tạp chí
Tiêu đề: A family of fifth-order Runge-Kutta pairs
2) C.W. Gear, “Numerical Initial Value Problems in Ordinary Differential Equations”, Prentice Hall, 1971.E. Harier, S.P Norsett, and G. Wanner. “Solving Ordinary Equations” – Springer Series in Computational Mathematics, 8. Springer, 2. edition, 2000 Sách, tạp chí
Tiêu đề: Numerical Initial Value Problems in Ordinary Differential Equations”,Prentice Hall, 1971. E. Harier, S.P Norsett, and G. Wanner. “Solving Ordinary Equations
3) Gunnar Staff. “Convergence and Stability of the Parareal algorithm: A numerical and theoretical investigation” – Department of Mathematical Sciences, Norwegian University of Science and Technology, N-7491 Trondheim, Norway Sách, tạp chí
Tiêu đề: Convergence and Stability of the Parareal algorithm: A numerical andtheoretical investigation
5) Evan Thomas. “Parallel Algorithms for Biologically Realistic, Large Scale Neural Modeling” – Department of Computer Science RMIT Sách, tạp chí
Tiêu đề: Parallel Algorithms for Biologically Realistic, Large Scale NeuralModeling
6) Edward A. Billard. “Distributed Computation” – California State University, Hayward Sách, tạp chí
Tiêu đề: Distributed Computation
7) Peter Pacheco. “Parallel Programming with MPI” Sách, tạp chí
Tiêu đề: Parallel Programming with MPI
8) Ian Foster. “Designing and Building Parallel Programs” Sách, tạp chí
Tiêu đề: Designing and Building Parallel Programs
9) William Gropp and Ewing Lusk “Tuning MPI Applications for Peak Performance” – Argonne National Laborary Sách, tạp chí
Tiêu đề: Tuning MPI Applications for Peak Performance
11) "Message Passing Interface (MPI)" tutorial from the Cornell Theory Center Sách, tạp chí
Tiêu đề: Message Passing Interface (MPI)
12) "RS/6000 SP Switch Performance" whitepaper. Frank May, IBM Corporation.June 1998 Sách, tạp chí
Tiêu đề: RS/6000 SP Switch Performance
15) Edsger W. Dijkstra Technological University, Eindhoven, The Netherlands The Structure of the "THE"-Multiprogramming System Commun. ACM 11 (1968), 5: 341–346 Sách, tạp chí
Tiêu đề: THE
Tác giả: Edsger W. Dijkstra Technological University, Eindhoven, The Netherlands The Structure of the "THE"-Multiprogramming System Commun. ACM 11
Năm: 1968
10) IBM Parallel Environment Manuals: www-1.ibm.com/servers/eserver/pseries/library/sp_books Khác
16) Solution of a Problem in Concurrent Programming Control Communications of Khác

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w