2022/7/25

Podman

Redhat 8 開始,提供了 podman,取代 docker,podman 是一種 daemonless container engine,使用時,可直接 alias docker=podman 完全取代 docker 的指令。

docker daemon 有個問題,如果 daemon 不存在,所有的 container 都無法運作。且 docker daemon 是所有 container process 的 parent process,所有 docker 的指令,都必須使用有 root 權限的使用者。

podman 直接與 image registry 溝通,而不是透過 daemon process 處理。 buildah 替代 Docker build,而跟 Image Registry 的溝通則被 skopeo取代。跟其他的 container 一樣,podman 是透過 OCI compliant 的 container runtime (runc, crun, runv) 作為 OS 的介面,執行 containers。

安裝

就直接用 dnf 安裝

dnf module install container-tools -y

以下是一些常用的指令

# search httpd image
podman search httpd --filter=is-official

# pull image
podman pull docker.io/library/httpd

# run httpd image
# -d 代表要列印 container ID
# -t 代表要加上 peudo-tty,才能用 interactive shell 執行任意指令
podman run -dt -name httpd1 -p 8080:80/tcp docker.io/library/httpd

# list images
podman images

# list process
podman ps

# start/stop
podman start httpd1
podman stop httpd1

# log
podman logs -l

service

因 podman 沒有 daemon process,故開機啟動時,無法像 docker 一樣,可以在 docker daemon process 啟動時,同時啟動 container。這時候必須要透過 system service 處理系統開機的自動啟動。

vi /etc/systemd/system/httpd1.service

[Unit]
Description=Graphite Docker Container
[Service]
Type=simple
TimeoutStartSec=0
Restart=on-failure
RestartSec=30s
ExecStart=/usr/bin/podman start -a httpd1
SyslogIdentifier=graphite
ExecStop=/usr/bin/podman stop -t 10 httpd1
[Install]
WantedBy=multi-user.target

然後用 systemd 啟動

systemctl daemon-reload

systemctl enable httpd1

systemctl start httpd1

References

Podman 淺談 - 為何你應該選擇 Podman 而不是 Docker? - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

Podman 初次体验 | Yiran's Blog

Podman 容器设置开机启动 | 码农家园

用systemd设置podman 生成的容器开机自启动_lggirls的博客-CSDN博客_podman systemd启动

2022/7/11

如何建立 CentOS 7 Local Repo

安裝 httpd

yum install httpd

建立 local repo

yum install createrepo yum-utils

mkdir -p /var/www/html/repos/centos/{base,centosplus,extras,updates}

用 reposync 同步 packages

reposync -g -l -d -m --repoid=base --newest-only --download-metadata --download_path=/var/www/html/repos/centos/
reposync -g -l -d -m --repoid=centosplus --newest-only --download-metadata --download_path=/var/www/html/repos/centos/
reposync -g -l -d -m --repoid=extras --newest-only --download-metadata --download_path=/var/www/html/repos/centos/
reposync -g -l -d -m --repoid=updates --newest-only --download-metadata --download_path=/var/www/html/repos/centos/

vi /etc/cron.weekly/update-centos-repo

#!/bin/bash
##specify all local repositories in a single variable
LOCAL_REPOS="base centosplus extras updates epel"
##a loop to update repos one at a time 
for REPO in ${LOCAL_REPOS}; do
    reposync -g -l -d -m --repoid=$REPO --newest-only --download-metadata --download_path=/var/www/html/repos/centos/
    #createrepo -g comps.xml /var/www/html/repos/centos/$REPO/
    createrepo --update /var/www/html/repos/centos/$REPO/
done

啟動 script

chmod 755 /etc/cron.weekly/update-centos-repo

設定 Local Repo

vim /etc/yum.repos.d/local-repos.repo

[local-base]
name=CentOS Base
baseurl=http://repos.server.ip/repos/centos/base/
gpgcheck=0
enabled=1

[local-centosplus]
name=CentOS CentOSPlus
baseurl=http://repos.server.ip/repos/centos/centosplus/
gpgcheck=0
enabled=1

[local-extras]
name=CentOS Extras
baseurl=http://repos.server.ip/repos/centos/extras/
gpgcheck=0
enabled=1

[local-updates]
name=CentOS Updates
baseurl=http://repos.server.ip/repos/centos/updates/
gpgcheck=0
enabled=1

測試

yum repolist

References

How to Setup Local HTTP Yum Repository on CentOS 7

建立 local http yum repository on Centos 7

Setup local YUM/DNF repo server Rocky Linux 8 [Step-by-Step] | GoLinuxCloud

Create a Centos Repository Mirror – UNIX fu

2022/7/4

認知無線電 Cognitive Radio, CR

1999年Joseph Mitola博士提出認知無線電 Cognitive Radio 的概念,最初定義為「一種能感知周圍環境並能適應環境的無線電」,CR具有學習能力,能與周圍環境交換資訊,感知和利用在該空間的可用頻譜,並限制和降低衝突。

認知無線電網路在認知過程中主要完成四個功能:頻譜感知、頻譜管理、頻譜共享和頻譜移動。Mitola的認知無線電是以軟體無線電爲平台,並讓軟體無線電智能化,強調有「學習」能力。

美國 FCC 提出了 CR 的簡化版本,建議任何具有動態根據環境改變發射參數的無線電,都可稱為 CR。無線電裝置能夠自動判斷頻道的狀態,並切換到可用的頻道。

兩個或多個具有認知無線電功能的無線電設備要建立一個通訊通道,必須先有一個同意採用什麼頻率、功率和調變機制的方法。一種技術是使用基地台在參與設備之間傳遞訊息。基地台只用於交換連線資料,然後認知無線電設備切換到協商一致的設定上。

在通話過程中,認知無線電設備必須連續監測通道和其它頻率,因為其它(非認知無線電)授權用戶可能突然啟動通話。他們比認知無線電設備的優先順序高,必須避免有害的干擾。在這種可能發生的情形中,認知無線電的作用是檢測授權用戶,建立並同意另一個閒置頻率,然後中斷當時的連接設定。

應用

  1. WRAN

    目的就是使用CR技術將分配給電視廣播的VHF/UHF頻帶(北美為54~862MHz)的頻率用作寬帶訪問線路,有效利用空閒頻道。IEEE 802.22 於2005年9月完成了對WRAN的功能需求和信道模型文檔,並於2006年3月形成了最終的合併提案作為編寫標準的基礎。

  2. UWB

    UWB技術產生於20世紀60年代,當時主要應用於脈衝雷達(ImpulseRadar),美國軍方利用其進行安全通信中的精確定位和成像。至20世紀90年代之前,UWB主要應用於軍事領域,之後UWB技術開始應用於民用領域。UWB由於具有傳輸速率高、系統容量大、抵抗多徑能力強、功耗低、成本低等優點,被認為是下一代無線通信的革命性技術,而且是未來多媒體寬帶無線通信中最具潛力的技術。

  3. WLAN

    IEEE802.11 標準為基礎的無線技術已經成為WLAN技術的主流,認知無線電技術能通過不斷掃描頻譜段,獲得這些可用頻道的環境和質量的認知資訊,自適應地接入較好的通訊頻道,這正是解決WLAN頻段擁擠問題的方法。

  4. Mesh

    無線Mesh網絡是近年來出現的具有一種無線多跳(Multi-hop)的網絡結構。在Mesh網絡中,每個節點可以和一個或者多個對等節點直接通信;同時也能模擬路由器的功能,從鄰近節點接收消息並進行中繼轉發。Mesh網絡通過鄰近節點之間的低功率傳輸取代了遠距離節點間的大功率傳輸,實現了低成本的通訊。網絡中所有節點之間是相互協作的,如果Mesh網絡中的一條鏈路失效了,網絡可以通過替代鏈路將信息路由到目的地,優化了頻譜的使用。

    認知無線電和無線Mesh結合,是在增大網絡密度和提高服務吞吐量的發展趨勢下提出來的,適用於可能有嚴重的線路競爭情況的人口密集城市的無線寬頻。認知Mesh網絡通過中繼方式可以有效地擴展網路覆蓋範圍,當一個無線Mesh網的骨幹網絡是由認知接入點和固定中繼點組成時,無線Mesh網的覆蓋範圍能夠大大增加。

  5. Ad-Hoc

    一般的 Ad-hoc網絡在發送資料前會先確定路由。認知無線電技術能夠即時收集資訊並向各方通知尚未使用的頻率信息,適用於具有不可提前預測的頻譜使用模式的應用場景。當認知無線電技術應用於低功耗多跳Ad-hoc網絡,能夠滿足分佈式認知用户之間的通信需求。基於認知無線電技術的Ad-hoc網絡需要新的支持分佈式頻率共享的MAC協定和路由協定。

DFS

802.11a工作於5GHz頻帶(在美國為U-NII頻段:5.15-5.25GHz、5.25-5.35GHz、5.725-5.825GHz),它採用OFDM(正交分頻多工)技術。802.11a支援的資料速率最高可達54Mbps。

但在歐洲軍方的雷達系統廣泛運用這一頻率(其中探測隱型飛機的雷達就使用這一頻率)。在歐洲出售的WLAN產品必須具備TPS和DFS這兩個功能,即發射功率控制和動態頻率選擇。TPS是為了防止無線產品發放過大的功率來干擾軍方雷達。DFS是為了使無線產品主動探測軍方使用的頻率,並主動選擇另一個頻率,以避開軍方頻率。通過這種方式,也可以避免其它WLAN干擾。

References

2022/6/27

安裝 project-open

project-open 有提供 docker image,也可以直接安裝,以下記錄安裝 project-open 的過程。

CentOS7

參考網頁

http://www.project-open.com/en/install-rhel-7

  1. system tools

    timedatectl set-timezone Asia/Taipei
    
    yum -y install system-config-*
    
    yum -y group install "System Administration Tools" --setopt=group_package_types=mandatory,default,optional
  2. KDE Plasma Workspaces Install "KDE Plasma Workspaces" Graphical Environment GNOME 3 on CentOS 7 has a number of important issues, so the ]po[ team recommends to switch to KDE:

    #yum -y group install "KDE Plasma Workspaces" "X Window System"
    #yum -y group install "Graphical Administration Tools" --setopt=group_package_types=mandatory,default,optional
    yum -y install net-tools setools policycoreutils-python
    #ln -sf /lib/systemd/system/graphical.target /etc/systemd/system/default.target
  3. Development Tools

    yum -y group install "Development Tools" --setopt=group_package_types=mandatory,default,optional
    yum -y group install "Compatibility Libraries"
    yum -y install vim emacs-nox
    yum -y install cvs svn git wget libXaw expat expat-devel pango graphviz graphviz-devel ImageMagick
    yum -y install libdbi-dbd-pgsql openldap-clients openldap-devel mlocate sharutils psmisc
  4. Perl Libraries

    yum -y install graphviz-perl perl perl-Archive-Tar perl-Archive-Zip perl-CGI perl-CGI-Session
    yum -y install perl-CPAN perl-CPAN-Changes perl-CPAN-Meta perl-CPAN-Meta-Requirements perl-CPAN-Meta-YAML
    yum -y install perl-Carp perl-Compress-Raw-Bzip2 perl-Crypt-DES perl-Crypt-OpenSSL-RSA
    yum -y install perl-Crypt-OpenSSL-Random perl-Crypt-PasswdMD5 perl-Crypt-SSLeay perl-DBD-Pg
    yum -y install perl-DBD-Pg-tests perl-DBI perl-Data-Dumper perl-DateTime perl-Digest-MD5
    yum -y install perl-Encode perl-File-Slurp perl-GSSAPI perl-IO-Socket-IP perl-IO-Socket-SSL
    yum -y install perl-JSON perl-LDAP perl-LWP-MediaTypes perl-LWP-Protocol-https perl-Net-DNS
    yum -y install perl-Net-HTTP perl-Net-SSLeay perl-Params-Check perl-Params-Util perl-Params-Validate
    yum -y install perl-Socket perl-TimeDate perl-WWW-Curl perl-YAML perl-core perl-devel perl-gettext
    yum -y install perl-libs perl-libwww-perl rrdtool-perl perl-YAML
  5. OpenOffce/LibreOffice

    yum -y install libreoffice libreoffice-headless
  6. 將 projop 加入 wheel

    vi /etc/group
    
    wheel:x:10:projop
  7. 修改 hosts

    vi /etc/hosts
    
    127.0.0.1 localhost
    10.140.0.3 po
  8. download files

    wget http://sourceforge.net/projects/project-open/files/project-open/Support%20Files/naviserver-4.99.8.tgz 
    wget http://sourceforge.net/projects/project-open/files/project-open/Support%20Files/web_projop-aux-files.5.0.0.0.0.tgz 
    wget http://sourceforge.net/projects/project-open/files/project-open/V5.0/update/project-open-Update-5.0.3.0.0.tgz 
  9. po installer

    groupadd projop                                                              # create a group called "projop"
    mkdir /web/                                                                  # super-directory for all Web servers /web/ by default
    useradd -d /web/projop -g projop projop                                      # create user "projop" with home directory /web/projop
    cd /web/projop/
    tar xzf /usr/src/web_projop-aux-files.5.0.0.0.0.tgz                          # extract auxillary files
    tar xzf /usr/src/project-open-Update-5.0.3.0.0.tgz                           # extract the ]po[ product source code - latest
    chown -R projop:projop /web/projop                                           # set ownership to all files
    
    cd /usr/local
    tar xzf /usr/src/naviserver-4.99.8.tgz                                       # extract the NaviServer binary 64 bit
  10. Setup PostgreSQL 9.2

    yum -y install postgresql postgresql-server postgresql-contrib postgresql-devel postgresql-odbc postgresql-docs
    
    systemctl enable postgresql

    init PostgreSQL

    /usr/bin/postgresql-setup initdb
    systemctl start postgresql
  11. Database

    su - postgres -c "createuser -s projop"                             # database user "projop" with admin rights
    su - projop -c "createdb --encoding=utf8 --owner=projop projop"     # new database
    su - projop -c "createlang plpgsql projop"                          # enable PlPg/SQL, may already be installed
  12. verify

    su - projop -c psql
    
    # Enter "\q" or press Ctrl-D to exit

    匯入 DB sql

    # import db
    
    su - projop
    psql -f ~/pg_dump.5.0.3.0.0.sql > import.log 2>&1
    
    # verify
    
    psql -c "select count(*) from users"
  13. 修改 postgresql.conf

    vi /var/lib/pgsql/data/postgresql.conf
    
    listen_addresses = '*'
    max_connections = 100
    
    #shared_buffers = 512MB
    shared_buffers = 256MB
    
    work_mem = 64MB
    maintenance_work_mem = 16MB
    
    checkpoint_segments = 64
    
    log_timezone = 'Asia/Taipei'
    timezone = 'Asia/Taipei'
  14. update pg_hba.conf

    vi /var/lib/pgsql/data/pg_hba.conf
    
    local   all             all                                     peer
    host    all             all             127.0.0.1/32            trust
    host    all             all             ::1/128                 ident
  15. update po config

    vi /web/projop/etc/config.tcl
    
    set httpport            8000
    set httpsport           8443
    
    set servername   "maxkit \]project-open\[ Server"
    set homedir      /usr/local/ns
  16. manual startup

    /usr/local/ns/bin/nsd -f -t /web/projop/etc/config.tcl -u projop -g projop
  17. service

    vi /usr/lib/systemd/system/projop.service
    
    [Unit]
    Description=NaviServer Web Server as user projop
    After=postgresql.service network.target
    Wants=postgresql.service
    
    [Service]
    Type=forking
    PIDFile=/web/projop/log/nsd.pid
    
    ExecStartPre=/usr/bin/rm -f /web/projop/log/nsd.pid
    ExecStart=/usr/local/ns/bin/nsd -t /web/projop/etc/config.tcl -u projop -g projop &
    ExecReload=/bin/kill -s HUP $MAINPID
    ExecStop=/bin/kill -s 9 $MAINPID
    
    Restart=always
    
    # Restart=on-abort
    
    # Restart=on-abnormal
    
    KillMode=process
    
    [Install]
    WantedBy=multi-user.target
    # init service
    chmod 755 /usr/lib/systemd/system/projop.service
    systemctl daemon-reload
    systemctl enable projop.service
    systemctl start projop.service
    
    tail -f /web/projop/log/error.log
  18. nginx

    yum -y install epel-release
    yum -y install nginx
    vi /etc/nginx/nginx.conf
    
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
    
    events {
       worker_connections 1024;
    }
    
    http {
       log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                         '$status $body_bytes_sent "$http_referer" '
                         '"$http_user_agent" "$http_x_forwarded_for"';
       access_log          /var/log/nginx/access.log  main;
       sendfile            on;
       tcp_nopush          on;
       tcp_nodelay         on;
       keepalive_timeout   601;
       types_hash_max_size 2048;
       default_type        application/octet-stream;
       include             /etc/nginx/mime.types;
       include             /etc/nginx/conf.d/*.conf;
    
       server {
           listen 80;
           location / {
               # pass all communication to NaviServer on port 8000
               proxy_pass           http://127.0.0.1:8000;
               # add information about the original IP
               proxy_set_header     X-Forwarded-For $remote_addr;
               # upload files to file storage up to 1G
               client_max_body_size 1024M;
           }
    
           # error_page    500 502 503 504 /err/50x.html;
           # error_page    404             /err/404.html;
           # location /err/ {
           #     root /usr/share/nginx/html;
           # }
    
       }
    
    }

    先用 port 80 瀏覽一次網頁

    cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx
    semodule -i mynginx.pp

Add Swap to CentOS 7

ref: How to Add Swap Space on CentOS 7 - Google Cloud

```
# 1G

dd if=/dev/zero of=/swapfile bs=1024 count=1048576

# 2G

dd if=/dev/zero of=/swapfile bs=1024 count=2097152

# 4G

dd if=/dev/zero of=/swapfile bs=1024 count=4194304

chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

swapon --show

```

```
vi /etc/fstab

/swapfile swap swap defaults 0 0

```

Docker

ref: http://www.project-open.com/en/install-docker-centos7

  1. CentOS 7安裝 docker

    yum install -y yum-utils device-mapper-persistent-data lvm2
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum install docker-ce
    
    systemctl enable docker
    systemctl start docker
  2. 下載 project-open

    mkdir -p download/project-open
    
    cd download/project-open
    
    wget --no-check-certificate https://sourceforge.net/projects/project-open/files/project-open/V5.0/older/project-open-Docker-Community-5.0.2.4.0.beta5.zip/download -O project-open-Docker-Community-5.0.2.4.0.beta5.zip
    
    unzip project-open-Docker-Community-5.0.2.4.0.beta5.zip
    cd po5-centos7
  3. 修改 setup line 24

    if ! wget --no-check-certificate -nv "$url" ; then
  4. 安裝

    ./setup
    ./build
    
    docker images
    
    REPOSITORY TAG IMAGE ID CREATED SIZE
    local/po5-centos7 latest acc4736e6a0c 7 minutes ago 1.81GB
  5. 啟動

    ./run
    
    docker start po5_centos7
    docker update --restart unless-stopped po5_centos7

2022/6/13

C# Thread

Thread 相關類別

有三個相關的類別:Thread, ThreadStart, ParameterizedThreadStart

  • ThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立無參數的委派函式

  • ParameterizedThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立有參數的委派函式

  • Thread

    建立、控制、管理 Thread,常用的屬性:

    屬性 說明 回傳資料型別
    CurrentThread 目前正在執行的 thread Thread
    IsAlive thread 的執行狀態 Boolean
    IsBackground thread 是否為背景執行緒 Boolean
    ManagedThreadId thread 的識別號碼 Integer
    Name thread 的名稱 String 或 null
    ThreadState thread 的狀態 ThreadState (enum)

    常用 method

    method 說明
    Abort() 停止 thread,停止後,無法重新啟動
    BeginCriticalRegion() 設定 Critical region
    EndCriticalRegion() 結束 Critical region
    Interrupt() 中斷 WaitSleepJoin 狀態的 thread
    Join([a]) 封鎖執行緒直到停止執行為止,a 為 integer (ms) 或 TimeSpan
    ResetAbort() 取消正在要求的 Abort()
    Sleep(a) 暫停執行緒 a (ms) 或 TimeSpan
    Start([a]) 啟動 thread,a為委派函式的參數,object 型別

    Note: 使用 Abort() 不保證一定能停止 thread,有可能會發生 exception。ex: SecurityException: 沒有權限停止 thread,ThreadStateException: 停止已暫停的 thread

  • ThreadState (enum)

    位於 System.Threading namespace

    列舉常數 value 說明
    Aborted 256 執行緒目前無作用,但狀態尚未變更為 Stopped
    AbortRequested 128 已收到 Abort()
    Background 4 背景執行
    Running 0 正在執行
    Stopped 16 已停止
    StopRequested 1 已被要求停止中
    Suspended 64 已暫停
    SuspendedRequested 2 已被要求暫停中
    Unstarted 8 還沒開始執行,未被呼叫 Start()
    WaitSleepJoin 32 已被封鎖

    列出呼叫哪個 method 會導致狀態改變

    method state
    建立 thread Unstarted
    Start() Running
    Sleep() WaitSleepJoin
    對另一個物件呼叫 Monitor.Wait() WaitSleepJoin
    Join WaitSleepJoin
    Interrupt() Runing
    Suspend() SuspendedRequested
    回應 Suspend() Suspended
    Resume() Running
    Abort() AbortRequested
    回應 Abort() Stopped
    thread 已終止 Stopped

建立 thread

  1. 建立委派的 method

  2. 使用 ThreadStart 建立委派物件

  3. 使用委派物件建立 Thread 型別的 Thread 物件

  4. Start()

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone;
        Int32 sum;
        int guess;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {            
            while (sum < int.MaxValue)
                sum++;

            fgDone = true;
        }

        void count_param(object num)
        {
            Random rd = new Random();

            while(guess!=(int)num)
            {
                guess = rd.Next(1, 101);
                Thread.Sleep(100);
            }                     

            fgDone = true;
        }

        // 沒有用 thread,計算過程中,視窗會卡住,無法使用
        private void Button1_Click(object sender, EventArgs e)
        {          
            textBox1.AppendText("開始計算\r\n");
            sum = 0;

            while (sum < int.MaxValue)
                sum++;

            textBox1.AppendText("計算完畢,sum= " + 
                sum.ToString()+"\r\n");
        }

        // 用 ThreadStart 產生 Thread
        private void button2_Click(object sender, EventArgs e)
        {
            ThreadStart thdStart = new ThreadStart(count);
            Thread thd = new Thread(thdStart);

            sum = 0;
            fgDone = false;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            timer1.Enabled = true;
        }

        // 用 ParameterizedThreadStart 產生 Thread
        private void button3_Click(object sender, EventArgs e)
        {
            int num = 78;
            ParameterizedThreadStart paramStart = 
                new ParameterizedThreadStart(count_param);
            Thread thd = new Thread(paramStart);

            fgDone = false;
            guess = -1;
            thd.Start(num);
            timer2.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (fgDone)
            {
                timer1.Enabled = false;
                textBox1.AppendText("計算完畢,sum= " +
                    sum.ToString()+"\r\n");
            }
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess.ToString() + "\r\n");
            if (fgDone)
            {
                timer2.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }
    }
}

取得 thread 執行結果

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Int32 sum;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {
            while (sum < int.MaxValue)
                sum++;           
        }

        void count_param(object param)
        {
            Int32[] pp = (Int32[])param;

            while (pp[0] < int.MaxValue)
                pp[0]++;           
        }

        // 透過 sum 全域變數,儲存 thread 的執行結果
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count);

            sum = 0;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            textBox1.AppendText("使用Join()方法,"+"" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum=" + sum.ToString() + 
                "\r\n");            
        }

        // 透過傳入 thread 的參數,儲存 thread 的執行結果
        // 因為該參數是 array,傳入是 call by value
        // 會將 sum1[0] 的記憶體位址傳給該 method
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_param);
            Int32 []sum1 = { 0 };

            textBox1.AppendText("執行緒開始執行\r\n");
            thd.Start(sum1);
            textBox1.AppendText("使用Join()方法," + "" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum1=" + sum1[0].ToString() + 
                "\r\n");
        }
    }
}

thread 生命週期

Thread 提供 Abort(), Join(), Interrupt(), Sleep(), Suspend(), Resume(),其中 Suspend(), Resume() 已經建議不要使用。

Join() 會等待 thread 結束,呼叫 Join() 的 thread 會持續等待無法回應。

結束 Thread 的方法,是讓該 Thread 完工後自行結束,如果是持續工作的 Thread,就要用 Abort(),或是用全域變數判斷要不要繼續執行。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone_a, fgDone_b;
        int guess_a, guess_b;
        Thread th_a=null;
        bool fgRun;
        int num_a = 78, num_b=50;

        public Form1()
        {
            InitializeComponent();
        }

        // 亂數產生數字,直到該數字為 78
        // 結束時,設定 fgDone_a,並停止 timer1
        void count_a()
        {
            Random rd = new Random();
            try
            {
                while (guess_a != num_a)
                {
                    guess_a = rd.Next(1, 101);
                    Thread.Sleep(100);
                }

                fgDone_a = true;
            }
            catch (ThreadAbortException ex)
            {
                timer1.Enabled = false;
            }
            catch (ThreadInterruptedException ex)
            {
                timer1.Enabled = false;
            }
        }

        void count_b()
        {
            Random rd = new Random();

            while (guess_b != num_b && fgRun)
            {
                guess_b = rd.Next(1, 101);
                Thread.Sleep(100);
            }

            fgDone_b = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess_a.ToString() + "\r\n");
            if (fgDone_a)
            {
                timer1.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }

        // 用 count_a 產生 Thread th_a
        // 同時啟動 timer1,timer1 會呼叫 timer1_Tick
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_a);

            th_a = thd;
            fgDone_a = false;
            guess_a = -1;
            thd.Start();
            timer1.Enabled = true;
        }

        // 呼叫 th_a.Abort()
        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("使用Abort()中止執行緒\r\n");
            th_a.Abort();
        }

        // 呼叫 th_a.Interrupt()
        private void button3_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("Interrupt()中斷執行緒\r\n");
            th_a.Interrupt();
        }

        // 用 count_b 產生 Thread th_b
        // 將全域變數 fgRun 設定為 true
        // 同時啟動 timer2,timer2 會呼叫 timer2_Tick
        private void button4_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_b);

            fgDone_b = false;
            guess_b = -1;
            fgRun = true;
            thd.Start();
            timer2.Enabled = true;
        }

        // 將全域變數 fgRun 設定為 false
        private void button5_Click(object sender, EventArgs e)
        {
            fgRun = false;
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox2.AppendText(guess_b.ToString() + "\r\n");
            if (fgDone_b)
            {
                timer2.Enabled = false;
                if(!fgRun)
                    textBox2.AppendText("結束執行緒\r\n");
                else
                    textBox2.AppendText("找到了\r\n");
            }
        }

        // 關閉 Form1,要停止 th_a,fgRun 設定為 false
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if(th_a!=null)
                th_a.Abort();

            fgRun = false;
        }        
    }
}

Thead 存取表單控制項

Form UI 控制項是由 UI Thread 控制,如果自己產生的 Thread 要跨到 UI thread 存取控制項,會發生錯誤。

前面的例子,是在 Thread 裡面計算後,將過程記錄在全域變數中,然後在 UI Thread 以 Timer 定時將資料顯示在 UI 上,這種方法比較麻煩。

透過控制項的 InvokeRequired 屬性及 Invoke(),可讓 Thread 直接存取控制項。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {        
        delegate void SafeCall(string str);
        Thread []myThreads= { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl() //無參數
        {
            if (textBox1.InvokeRequired)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                MethodInvoker ivk = new MethodInvoker(safeControl);
                textBox1.Invoke(ivk, new object[] { });
            }
            else
                textBox1.AppendText("myThreads[1]: " + 
                    sum.ToString() + "\r\n");
        }

        void myFunc()
        {
            while (true)
            {                    
               safeControl();
               Thread.Sleep(500);
            }          
        }

        // 用 InvokeRequired 判斷 textBox1 存取權
        // 如果是 true,就表示現在是 UI thread 在控制
        // 就要由 textBox1 呼叫一次 SafeCall
        // false 就表示為外部 thread 存取,可直接使用 textBox1
        void safeControl_param(string str) //有參數
        {
            if (textBox1.InvokeRequired)
            {
                SafeCall ivk = new SafeCall(safeControl_param);
                textBox1.Invoke(ivk, new object[] { str });
            }
            else
                textBox1.AppendText(str + "\r\n");
        }

        // myThreads[0] 的 method
        // 呼叫 safeControl_param
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                safeControl_param("myThreads[0]: " + num.ToString());
                Thread.Sleep(500);
            }                                
        }

        // 用 button1 產生 myThreads[0]
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

           // 如果 myThreads[0] 已經存在,就要停止 myThreads[0]
           // 用 myFunc_param 重新產生一個 myThreads[0],並啟動
           if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        // 用 button2 產生 myThreads[1]
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach(var item in myThreads)
                if (item != null)
                    item.Abort();           
        }        
    }
}

另一種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread[] myThreads = { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl_param(string str) //有參數
        {
            textBox1.Invoke(new Action (() =>
            {
               textBox1.AppendText(str);
            }
            ));
        }

        // this.Invoke 配合 new Action 
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);

                //也能寫成函式來來呼叫:safeControl_param(num.ToString());
                this.Invoke(new Action(() =>
                {
                    textBox1.AppendText("myThreads[0]: "+num.ToString()+"\r\n");
                }
             ));

            Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            sum++;
            if (sum > int.MaxValue)
                sum = 0;

            this.Invoke((MethodInvoker)delegate 
            { textBox1.AppendText( "myThreads[1]: " +
                    sum.ToString() + "\r\n"); }
            );
        }

        void myFunc()
        {
            while (true)
            {
                ////此段程式也能寫成函式來來呼叫:safeControl();
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;

                this.Invoke((MethodInvoker)delegate
                {
                    textBox1.AppendText("myThreads[1]: " +
                          sum.ToString() + "\r\n");
                }
                );

                Thread.Sleep(500);
            }
        }


        // 有參數的 myFunc_param Thread
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThreads)
                if (item != null)
                    item.Abort();
        }

        //  沒有參數的 myFunc Thread
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }
    }
}

第三種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        delegate void SafeCall(string str);
        Thread []myThread= { null, null };
        int sum;

        public Form1()
        {
            InitializeComponent();
        }

        void myFunc2()
        {
            MethodInvoker ivk = new MethodInvoker(safeControl);

            while (true)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                this.Invoke(ivk, new object[] { });
                Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            textBox1.AppendText(sum.ToString() + "\r\n");
            label1.Text = sum.ToString() ;

        }

        void myFunc1()
        {
            SafeCall ivk = new SafeCall(safeControl_param);
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                this.Invoke(ivk, new Object[] { num.ToString()+"\r\n" });
                Thread.Sleep(500);
            }
        }

        private void safeControl_param(string str)
        {
            textBox1.AppendText(str);
            label1.Text = str;

        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc1);
            myThread[0] = thd;

            thd.Start();

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThread)
                if (item != null)
                    item.Abort();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc2);
            myThread[1] = thd;

            sum = 0;
            thd.Start();
        }
    }
}

執行緒同步

如果有多個 thread 會同時存取相同的資源,會造成內容一致性的問題。有四個方法,可處理 synchronization 問題

  • synchronized code region -> 最常用

    當 thread A 執行時,thread B 必須等待 A 完成後,才能執行該區塊

  • manual synchronization

    使用 .Net Framework 的 Mutex, Semaphore, EventWaitHandle, AutoResetEvent, ManualResetEvent

  • synchronized contexts

    使用 SynchronizationAttribute 設定 ContextBoundObject

    當物件進入或離開由 ContextBoundObject 定義的內容時,會強制執行規則

  • 使用 System.Collections.Concurrent 的集合與類別

Critical Section: thread 存取的共用資源(物件、資料、變數、設備),同一時間只有一個 thread 能使用

lock()

互斥鎖: public object locker = new object();

lock(locker) 以 locker 鎖定 critical section

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1=null, thd2=null;
        object locker = new object();
        bool fg1 = false, fg2=false;

        public Form1()
        {
            InitializeComponent();
        }

        // 當 Form1 載入時,就啟動兩個 thread
        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        // 無窮迴圈
        // 當 fg1 為 true,就 lock locker 物件,執行 critical section 區塊
        // 完成後將 fg1 改為 false
        void func1()
        {
            while(true)
            {
                if(fg1)
                    lock(locker)
                    {
                        for(int i=0;i<10;i++)
                        {
                            textBox1.Invoke(new Action(() =>
                            {
                                textBox1.AppendText("Thread 1\r\n");
                            }));
                            Thread.Sleep(500);
                        }
                        fg1 = false;
                    }
            }
        }

        void func2()
        {
            while (true)
            {               
                if (fg2)
                    lock (locker)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                            textBox1.AppendText("Thread 2\r\n");
                        }));
                        fg2 = false;
                    }
            }        
        }

        // 將 fg1 改為 true
        private void button1_Click(object sender, EventArgs e)
        {                        
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }     
    }
}

Monitor

method 說明
Enter(a, [b]) 取得並鎖定互斥鎖 a,a為 Object,b 為 Boolean。當 b 為 true,表示已經取得互斥鎖 a,否則為 false
Exit(a) 釋放互斥鎖 a
IsEntered(a) 判斷 thread 是否已經取得互斥鎖 a
Pulse(a) 通知等候的 thread,互斥鎖 a 已改變狀態
PulseAll(a) 通知等候 queu 的所有 threads,互斥鎖 a 已改變狀態
TryEnter(a,b,c) 嘗試在時間 b 取得互斥鎖 a,並回傳結果 c。
a: Object, b: Int32 or TimeSpan, c: Boolean
TryEnter(a[,b]) 嘗試在時間 b 取得互斥鎖 a。
a: Object, b: Int32 or TimeSpan
TryEnter(a[,b]) 嘗試取得互斥鎖 a,並回傳結果 b。
a: Object, b: Boolean
Wait(a[,b[,c]]) 釋放互斥鎖 a ,並在時間 b 內,嘗試重新取得互斥鎖 a。如果無法取得,就會進入等候 queue
a: Object, b: Int32 or TimeSpan, c: Boolean

Enter, Exit 要搭配使用,否則會造成 a 無法釋放或 deadlock

Pulse() 只能被正在鎖定互斥鎖的 thread 呼叫

critical section

Monitor.Enter(locker);
.
.
.
Monitor.Exit(locker);
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1 = null, thd2 = null;
        object locker = new object();
        bool fg1 = false, fg2 = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        void func1()
        {
            while (true)
            {
                if (fg1)                    
                {
                    Monitor.Enter(locker);
                    for (int i = 0; i < 10; i++)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                                textBox1.AppendText("Thread 1\r\n");
                        }));
                        Thread.Sleep(500);
                    }
                    Monitor.Exit(locker);
                    fg1 = false;
                }
            }
        }

        void func2()
        {
            while (true)
            {
                if (fg2)                    
                {
                    Monitor.Enter(locker);
                    textBox1.Invoke(new Action(() =>
                    {
                        textBox1.AppendText("Thread 2\r\n");
                    }));
                    Monitor.Exit(locker);
                    fg2 = false;
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }
    }
}

Semaphore

5 人去店裡繳費,但只有 3 個櫃檯

一般來說臨櫃劉成為:取號碼牌、有空的櫃檯時叫號、沒有空的櫃檯就等待

Semaphore 提供機制協調多個 thread 的同步處理

// 有三個號誌數量,最多可接受3個 thread 要求號誌 
Semaphore smphore = new Semaphore(3,3);


// 有0個號誌數量,最多可接受3個 thread 要求號誌
// 一開始,所有 thread 都在等待 
Semaphore smphore = new Semaphore(0,3);

// 釋放 3 個號誌
smphore.Release(3);

// 沒有參數,表示釋放先前取得的號誌
smphore.Release();
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Semaphore smphore;
        Thread[] thds=new Thread[5];
        delegate void SafeCall(string str);

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl(string str)
        {
            textBox1.AppendText(str);
        }

        void func(object param)
        {
            int no = (int)param;
            string str;
            SafeCall ivk = new SafeCall(safeControl);

            str = String.Format("第{0}位在排隊...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str});

            // 所有 threads 會停在這裡等待 semaphore
            smphore.WaitOne();

            str = String.Format("第{0}位正在繳費...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            Thread.Sleep(1000);

            str = String.Format("第{0}位繳費結束...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            smphore.Release();
            // 結束時,釋放 semaphore
        }        

        private void Form1_Load(object sender, EventArgs e)
        {
            // 載入時,產生 semaphore
            smphore = new Semaphore(0, 3);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 產生並啟動 5 個 threads
            for (int i = 0; i < thds.Length; i++)
                thds[i] = new Thread(
                    new ParameterizedThreadStart(func));

            for (int i = 0; i < thds.Length; i++)
                thds[i].Start(i + 1);

            textBox1.AppendText("尚未開始營業...\r\n");
            Thread.Sleep(3000);

            textBox1.AppendText("開始營業...\r\n");
            smphore.Release(3);
        }
    }
}

References

博客來-C#程式設計從入門到專業(下):職場C#進階應用技術